Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/make swagger json links browsable [OSF-6163] #6099

Closed
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
effc3f3
Set up links to raw json responses of links in json responses.(what)
NyanHelsing May 20, 2016
559694f
Getting branch up to date
NyanHelsing May 21, 2016
eee8ae2
saving changes
NyanHelsing May 21, 2016
14e677f
Browsable json links!
NyanHelsing May 24, 2016
808b9c4
Tidy up JSON browsing script
NyanHelsing May 25, 2016
7755b3a
Fix an issue with the way swagger (introspects?) one of the routes.
NyanHelsing May 25, 2016
896c136
Fixes the way the node views interpret arguments embedded in the url
NyanHelsing May 25, 2016
e9e9d8e
formatting link correctly for waterbutler
Aug 5, 2016
69fd94b
flake8 fixes
Aug 8, 2016
3fafac4
Update views.py
NyanHelsing Aug 8, 2016
d9f3890
Forgot asterisks.
NyanHelsing Aug 8, 2016
66741d5
fixes
Aug 8, 2016
abc4370
fix merge conflict
Aug 11, 2016
2c73ff1
fix merge conflicts
Aug 11, 2016
d642954
fix node arguments formatting
Aug 11, 2016
d2ad2f7
ng osf to unicode strings
Aug 21, 2016
5032f7a
Merge branch 'develop' of https://github.com/CenterForOpenScience/osf…
Aug 22, 2016
f462407
fix flake8 errors
Aug 22, 2016
c91f90c
monkeypatch simplify_regex
Aug 22, 2016
64095a6
fix flake8 errors
Aug 22, 2016
ce77528
define path_components array
Aug 22, 2016
2ff8bd6
setting correct permissions for endpoints
Aug 22, 2016
6b25d6a
fix whitespace
Aug 22, 2016
6bd9fe1
fix flake errors
Aug 22, 2016
b29e1ad
updating to unicode, fix typo
Aug 23, 2016
fbcfb4a
Update tests to account for increase in second precision in datetime …
Aug 24, 2016
b9547bf
update bearer tokens to be passed into request constructor as bytestr…
Aug 24, 2016
fcd4eb9
update Tests to provide bytestring to Request constructor.
Aug 24, 2016
d72f561
remove unneccesary code
Aug 24, 2016
e2c2840
unused import
Aug 24, 2016
cfce105
updateing for new drf error message
Aug 24, 2016
fb16f01
updating tests to differentiate between 401 and 403 errors
Aug 24, 2016
88ff69d
updating tests
Aug 25, 2016
68a286f
fix broken test
Aug 25, 2016
764849d
fix tests
Aug 25, 2016
6c3df1c
remove dead code
Aug 25, 2016
2be2170
relocating swagger-ui js file
Aug 25, 2016
1f07750
Wierd. git doesn't want to do the things...
NyanHelsing Aug 25, 2016
4c462a6
removing dead code
Aug 25, 2016
4eab556
Merge branch 'feature/make-swagger-json-links-browsable' of https://g…
Aug 25, 2016
78279a9
fix tests
Aug 26, 2016
ca3ab5b
endpoint categories are now sorted in swagger-ui.
Aug 26, 2016
7ce405a
debugging missing paths
Aug 29, 2016
5ae48ee
Merge branch 'develop' of github.com:CenterForOpenScience/osf.io into…
Aug 29, 2016
f20e453
fix regex. fix api schema generation.
Aug 29, 2016
392a4ac
fix flake8 stuff
Aug 29, 2016
f5cb930
Merge branch 'develop' of github.com:CenterForOpenScience/osf.io into…
Aug 30, 2016
21283c3
Updating docs
Aug 30, 2016
18ff1aa
make model list alphabetical
Aug 30, 2016
7a9664b
code cleanup
Sep 1, 2016
c727d60
missing comma
Sep 1, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/addons/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.addons import views
Expand Down
3 changes: 2 additions & 1 deletion api/addons/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import importlib

from __future__ import unicode_literals
import importlib
from rest_framework.exceptions import NotFound
from rest_framework import generics, permissions as drf_permissions

Expand Down
1 change: 1 addition & 0 deletions api/applications/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.applications import views
Expand Down
2 changes: 2 additions & 0 deletions api/applications/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
"""
Views related to OAuth2 platform applications. Intended for OSF internal use only
"""
Expand Down
1 change: 0 additions & 1 deletion api/base/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,6 @@ def to_representation(self, value):

if url is None:
raise SkipField

related_url = url['related']
related_meta = self.get_meta_information(self.related_meta, value)
self_url = url['self']
Expand Down
3 changes: 2 additions & 1 deletion api/base/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,12 @@
STATICFILES_DIRS = (
('rest_framework_swagger/css', os.path.join(BASE_DIR, 'static/css')),
('rest_framework_swagger/images', os.path.join(BASE_DIR, 'static/images')),
('rest_framework_swagger/js', os.path.join(BASE_DIR, 'static/js')),
)

# TODO: Revisit methods for excluding private routes from swagger docs
SWAGGER_SETTINGS = {
'api_path': '/',
'api_path': 'v2/',
'info': {
'description':
"""
Expand Down
11 changes: 10 additions & 1 deletion api/base/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
Expand All @@ -19,8 +20,16 @@
url(r'^applications/', include('api.applications.urls', namespace='applications')),
url(r'^collections/', include('api.collections.urls', namespace='collections')),
url(r'^comments/', include('api.comments.urls', namespace='comments')),
url(r'^docs/', include('rest_framework_swagger.urls')),
url(r'^nodes/', include('api.nodes.urls', namespace='nodes')),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See L12 and please keep this list alphabetized (unless the new version of DRS auto-alphabetizes regardless if this list order, in which remove L12).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRS does alphabetize on the actual API -> Core api transition, though something weird happens between there and rendering on the documentation page.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorted out the alphabetization thing.

url(r'^registrations/', include('api.registrations.urls', namespace='registrations')),
url(r'^metaschemas/', include('api.metaschemas.urls', namespace='metaschemas')),
url(r'^users/', include('api.users.urls', namespace='users')),
url(r'^tokens/', include('api.tokens.urls', namespace='tokens')),
url(r'^logs/', include('api.logs.urls', namespace='logs')),
url(r'^files/', include('api.files.urls', namespace='files')),
url(r'^docs/', views.schema_view),
url(r'^institutions/', include('api.institutions.urls', namespace='institutions')),
url(r'^collections/', include('api.collections.urls', namespace='collections')),
url(r'^guids/', include('api.guids.urls', namespace='guids')),
url(r'^identifiers/', include('api.identifiers.urls', namespace='identifiers')),
url(r'^institutions/', include('api.institutions.urls', namespace='institutions')),
Expand Down
129 changes: 127 additions & 2 deletions api/base/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from __future__ import unicode_literals
import inspect
import weakref
from django.conf import settings as django_settings
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.response import Response
from rest_framework.compat import coreapi, uritemplate, urlparse
from rest_framework import generics
from rest_framework import exceptions
from rest_framework import status
from rest_framework import permissions as drf_permissions
from rest_framework import schemas
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
from rest_framework.request import clone_request
from rest_framework.exceptions import ValidationError, NotFound

from framework.auth.oauth_scopes import CoreScopes
Expand All @@ -25,9 +32,127 @@
from api.nodes.permissions import ContributorOrPublicForRelationshipPointers
from api.base.utils import is_bulk_request, get_user_auth


CACHE = weakref.WeakKeyDictionary()

class SchemaGenerator(schemas.SchemaGenerator):

#def __init__(self, title=None, url=None, patterns=None, urlconf=None):
# import ipdb; ipdb.set_trace()
# super(SchemaGenerator, self).__init__(**kwargs)

def get_schema(self, request=None):
if self.endpoints is None:
self.endpoints = self.get_api_endpoints(self.patterns)

links = []
for path, method, category, action, callback in self.endpoints:
view = callback.cls()
for attr, val in getattr(callback, 'initkwargs', {}).items():
setattr(view, attr, val)
view.args = ()
view.kwargs = {
'user_id': 'me'
}
view.format_kwarg = None
if request is not None:
view.request = clone_request(request, method)
try:
view.check_permissions(view.request)
except exceptions.APIException:
continue
else:
view.request = None
link = self.get_link(path, method, callback, view)
links.append((category, action, link))
if not links:
return None
content = {}
for category, action, link in links:
if category is None:
content[action] = link
elif category in content:
content[category][action] = link
else:
content[category] = {action: link}
doc = coreapi.Document(title=self.title, content=content, url=self.url)
return doc

def get_link(self, path, method, callback, view):
"""
Return a `coreapi.Link` instance for the given endpoint.
"""
fields = self.get_path_fields(path, method, callback, view)
fields += self.get_serializer_fields(path, method, callback, view)
fields += self.get_pagination_fields(path, method, callback, view)
fields += self.get_filter_fields(path, method, callback, view)

if fields and any([field.location in ('form', 'body') for field in fields]):
encoding = self.get_encoding(path, method, callback, view)
else:
encoding = None

if self.url and path.startswith('/'):
path = path[1:]

try:
description = inspect.getdoc(callback.cls())
except:
description = 'No Description'

link = coreapi.Link(
url=urlparse.urljoin(self.url, path),
action=method.lower(),
encoding=encoding,
fields=fields,
description=description
)
return link

def get_path_fields(self, path, method, callback, view):
"""
Return a list of `coreapi.Field` instances corresponding to any
templated path variables.
"""
fields = []
for variable in uritemplate.URITemplate(path).variable_names:
field = coreapi.Field(name=variable, location='path', required=True)
fields.append(field)
return fields

def get_category(self, path, method, callback, action):
"""
Return a descriptive category string for the endpoint, eg. 'users'.
"""

try:
vn = callback.cls().view_name
return vn
except:
pass

path_components = path.strip('/').split('/')
path_components = [
component for component in path_components
if '{' not in component
]

if action in self.known_actions:
idx = -1
else:
idx = -2

try:
return path_components[idx]
except IndexError:
return None

@api_view()
@renderer_classes([OpenAPIRenderer, SwaggerUIRenderer])
def schema_view(request):
generator = SchemaGenerator(title='OSF API')
res = Response(generator.get_schema(request=request))
return res


class JSONAPIBaseView(generics.GenericAPIView):

Expand Down
53 changes: 53 additions & 0 deletions api/base/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
"""
WSGI config for api project.

Expand Down Expand Up @@ -26,8 +28,58 @@
#### WARNING: Here be monkeys ###############
import six
import sys
import re
from django.contrib.admindocs import views
from rest_framework.fields import Field
from rest_framework.request import Request
import urlparse
from coreapi.compat import force_bytes
from openapi_codec import OpenAPICodec, encode
import simplejson as json

def generate_swagger_object(document):
"""
Generates root of the Swagger spec.
"""
parsed_url = urlparse.urlparse(document.url)

return {
'swagger': '2.0',
'info': encode._get_info_object(document),
'tags': _get_tags_object(document),
'paths': encode._get_paths_object(document),
'host': parsed_url.netloc,
}

def _get_tags_object(document):
return [{'name': tag, 'description': 'No Description'} for tag in sorted({tag for tag, _object in document.data.items()})]

def dump(self, document, **kwargs):
data = generate_swagger_object(document)
return force_bytes(json.dumps(data))

OpenAPICodec.dump = dump

named_group_matcher = re.compile(r'\(\?P(<\w+>)[^\)]+\)')
non_named_group_matcher = re.compile(r'\(.*?\)')

def simplify_regex(pattern):
"""
Clean up urlpattern regexes into something somewhat readable by Mere Humans:
turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
into "<sport_slug>/athletes/<athlete_slug>/"
"""
# handle named groups first
pattern = named_group_matcher.sub(lambda m: m.group(1), pattern)

# handle non-named groups
pattern = non_named_group_matcher.sub('<var>', pattern)

# clean up any outstanding regex-y characters.
pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '')
if not pattern.startswith('/'):
pattern = '/' + pattern
return pattern

# Cached properties break internal caching
# 792005806b50f8aad086a76ff5a742c66a98428e
Expand All @@ -44,6 +96,7 @@ def __getattr__(self, attr):
info = sys.exc_info()
six.reraise(info[0], info[1], info[2].tb_next)

views.simplify_regex = simplify_regex
Field.context = context
Request.__getattr__ = __getattr__
Request.__getattribute__ = object.__getattribute__
Expand Down
1 change: 1 addition & 0 deletions api/caching/tasks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
import urlparse

import requests
Expand Down
1 change: 1 addition & 0 deletions api/collections/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.collections import views
Expand Down
2 changes: 2 additions & 0 deletions api/collections/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from modularodm import Q
from rest_framework import generics, permissions as drf_permissions
from rest_framework.exceptions import ValidationError, NotFound, PermissionDenied
Expand Down
1 change: 1 addition & 0 deletions api/comments/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url
from api.comments import views

Expand Down
2 changes: 2 additions & 0 deletions api/comments/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from rest_framework import generics, permissions as drf_permissions
from rest_framework.exceptions import NotFound, ValidationError, PermissionDenied

Expand Down
3 changes: 3 additions & 0 deletions api/files/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

from __future__ import unicode_literals
from rest_framework import generics

from rest_framework import permissions as drf_permissions
from rest_framework.exceptions import NotFound

Expand Down
1 change: 1 addition & 0 deletions api/guids/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.guids import views
Expand Down
2 changes: 2 additions & 0 deletions api/guids/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
import furl
from django import http
from rest_framework.exceptions import NotFound
Expand Down
1 change: 1 addition & 0 deletions api/identifiers/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.identifiers import views
Expand Down
2 changes: 2 additions & 0 deletions api/identifiers/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from modularodm import Q
from rest_framework import generics, permissions as drf_permissions

Expand Down
1 change: 1 addition & 0 deletions api/institutions/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.institutions import views
Expand Down
2 changes: 2 additions & 0 deletions api/institutions/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from rest_framework import generics
from rest_framework import permissions as drf_permissions
from rest_framework import exceptions
Expand Down
1 change: 1 addition & 0 deletions api/licenses/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.licenses import views
Expand Down
2 changes: 2 additions & 0 deletions api/licenses/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from rest_framework import generics, permissions as drf_permissions
from framework.auth.oauth_scopes import CoreScopes

Expand Down
1 change: 1 addition & 0 deletions api/logs/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.logs import views
Expand Down
2 changes: 2 additions & 0 deletions api/logs/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from rest_framework import generics, permissions as drf_permissions
from rest_framework.exceptions import NotFound

Expand Down
1 change: 1 addition & 0 deletions api/metaschemas/urls.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.conf.urls import url

from api.metaschemas import views
Expand Down
2 changes: 2 additions & 0 deletions api/metaschemas/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

from __future__ import unicode_literals
from modularodm import Q
from rest_framework import generics, permissions as drf_permissions
from rest_framework.exceptions import NotFound
Expand Down
Loading