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

Problem: REST API docs are poorly organized #67

Merged
merged 1 commit into from
Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ diagrams:
mv diagrams_src/*.png $(DIAGRAM_BUILD_DIR)/

html:
curl -o api.yaml "http://localhost:8000/pulp/api/v3/docs/api.yaml"
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to add mkdir -p _build/html/ before the next line.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, mkdir -p ${BUILDDIR}/html/

mkdir -p $(BUILDDIR)/html
curl -o _build/html/api.json "http://localhost:8000/pulp/api/v3/docs/?format=openapi"
dkliban marked this conversation as resolved.
Show resolved Hide resolved
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
Expand Down
24 changes: 24 additions & 0 deletions docs/_templates/restapi.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">

<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='api.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
html_additional_pages = {'restapi': 'restapi.html'}

# If false, no module index is generated.
#html_domain_indices = True
Expand Down
2 changes: 1 addition & 1 deletion docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Glossary

pulpcore
A generalized backend with a Plugin API and a :doc:`REST
API<integration-guide/rest-api/index>`. It uses :term:`plugins<plugin>` to manage
API<integration-guide/index>`. It uses :term:`plugins<plugin>` to manage
:term:`content`.

PUP
Expand Down
5 changes: 1 addition & 4 deletions docs/integration-guide/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
REST API Integration
====================

.. toctree::
:maxdepth: 2

rest-api/index
The REST API for pulpcore can be `found here <../restapi.html>`_.
37 changes: 0 additions & 37 deletions docs/integration-guide/rest-api/authentication.rst

This file was deleted.

17 changes: 0 additions & 17 deletions docs/integration-guide/rest-api/index.rst

This file was deleted.

140 changes: 139 additions & 1 deletion pulpcore/app/openapigenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import re

from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg import openapi
from drf_yasg.utils import filter_none, force_real_str
import uritemplate


Expand All @@ -21,6 +23,24 @@ def __init__(self, paths, **extra):

class PulpOpenAPISchemaGenerator(OpenAPISchemaGenerator):

def __init__(self, info, version='', url=None, patterns=None, urlconf=None):
"""
Args:
info (drf_yasg.openapi.Info): information about the API
version (str): API version string; if omitted, `info.default_version` will be used
url (str): API scheme, host and port; if ``None`` is passed and ``DEFAULT_API_URL`` is
not set, the url will be inferred from the request made against the schema view, so
you should generally not need to set this parameter explicitly; if the empty string
is passed, no host and scheme will be emitted If `url` is not ``None`` or the empty
string, it must be a scheme-absolute uri (i.e. starting with http:// or https://),
and any path component is ignored.
patterns: if given, only these patterns will be enumerated for inclusion in the API spec
urlconf: if patterns is not given, use this urlconf to enumerate patterns;
if not given, the default urlconf is used
"""
self.tags = []
super().__init__(info, version=version, url=url, patterns=patterns, urlconf=urlconf)

def get_paths(self, endpoints, components, request, public):
"""Generate the Swagger Paths for the API from the given endpoints.

Expand Down Expand Up @@ -50,8 +70,15 @@ def get_paths(self, endpoints, components, request, public):

operation = self.get_operation(view, path, prefix, method, components, request)
if operation is not None:
operation.operation_id = operation.operation_id.replace('pulp_api_v3_', '')
operations[method.lower()] = operation
tag = operation.tags[0]
tag_dict = {"name": tag, "x-displayName": tag.title()}
tag_exists = False
for tag in self.tags:
if tag["name"] == tag_dict["name"]:
tag_exists = True
if not tag_exists:
self.tags.append(tag_dict)

if operations:
path_param = None
Expand Down Expand Up @@ -160,3 +187,114 @@ def get_parameter_name(model):
str: name of the resource associated with the model
"""
return ' '.join(re.findall('[A-Z][^A-Z]*', model.__name__))

def get_operation_keys(self, subpath, method, view):
"""Return a list of keys that should be used to group an operation within the specification. ::

/users/ ("users", "list"), ("users", "create")
/users/{pk}/ ("users", "read"), ("users", "update"), ("users", "delete")
/users/enabled/ ("users", "enabled") # custom viewset list action
/users/{pk}/star/ ("users", "star") # custom viewset detail action
/users/{pk}/groups/ ("users", "groups", "list"), ("users", "groups", "create")
/users/{pk}/groups/{pk}/ ("users", "groups", "read"), ("users", "groups", "update")

The path prefix, /pulp/api/v3/, is ignored.

Args:
subpath (str): path to the operation with any common prefix/base path removed
method (str): HTTP method
view (rest_framework.views.APIView): the view associated with the operation

Returns:
List of strings
"""
subpath = subpath.replace('/pulp/api/v3', '')
return super().get_operation_keys(subpath, method, view)

def get_schema(self, request=None, public=False):
"""Generate a :class:`.Swagger` object representing the API schema.

This method also adds tags to the schema definition. This allows ReDoc to provide a display
name for each section of the docs.

Args:
request (rest_framework.request.Request): the request used for filtering accessible
endpoints and finding the spec URI. Can be None.
public (bool): if True, all endpoints are included regardless of access through
`request`

Returns:
openapi.Swagger: The generated Swagger specification
"""
schema = super().get_schema(request=request, public=public)
schema.tags = self.tags
return schema


class PulpAutoSchema(SwaggerAutoSchema):
"""
Auto schema inspector for Pulp. This inspector is able to generate nice desriptions for all
operations.
"""

def get_operation(self, operation_keys):
consumes = self.get_consumes()
produces = self.get_produces()

body = self.get_request_body_parameters(consumes)
query = self.get_query_parameters()
parameters = body + query
parameters = filter_none(parameters)
parameters = self.add_manual_parameters(parameters)

operation_id = self.get_operation_id(operation_keys)
summary, description = self.get_summary_and_description()
security = self.get_security()
assert security is None or isinstance(security, list), "security must be a list of " \
"security requirement objects"
deprecated = self.is_deprecated()
tags = self.get_tags(operation_keys)

responses = self.get_responses()
summary = self.get_summary(operation_keys)
return openapi.Operation(
operation_id=operation_id,
description=force_real_str(description),
summary=force_real_str(summary),
responses=responses,
parameters=parameters,
consumes=consumes,
produces=produces,
tags=tags,
security=security,
deprecated=deprecated
)

def get_summary(self, operation_keys):
"""
Returns summary of operation.

This is the value that is displayed in the ReDoc document as the short name for the API
operation.
"""
if not hasattr(self.view, 'queryset'):
return self.get_summary_and_description()[0]
model = self.view.queryset.model
operation = operation_keys[-1]
resource = model._meta.verbose_name
article = 'a'
if resource[0].lower() in 'aeiou':
article = 'an'
if operation == 'read':
return f'Inspect {article} {resource}'
elif operation == 'list':
resource = model._meta.verbose_name_plural
return f'List {resource}'
elif operation == 'create':
return f'Create {article} {resource}'
elif operation == 'update':
return f'Update {article} {resource}'
elif operation == 'delete':
return f'Delete {article} {resource}'
elif operation == 'partial_update':
return f'Partially update {article} {resource}'
6 changes: 6 additions & 0 deletions pulpcore/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,9 @@
CONTENT_PATH_PREFIX = settings.get('CONTENT_PATH_PREFIX', '/pulp/content/')

PROFILE_STAGES_API = settings.get('PROFILE_STAGES_API', False)

SWAGGER_SETTINGS = {
'DEFAULT_GENERATOR_CLASS': 'pulpcore.app.openapigenerator.PulpOpenAPISchemaGenerator',
'DEFAULT_AUTO_SCHEMA_CLASS': 'pulpcore.app.openapigenerator.PulpAutoSchema',
'DEFAULT_INFO': 'pulpcore.app.urls.api_info',
}
11 changes: 5 additions & 6 deletions pulpcore/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from rest_framework_nested import routers

from pulpcore.app.apps import pulp_plugin_configs
from pulpcore.app.openapigenerator import PulpOpenAPISchemaGenerator
from pulpcore.app.views import OrphansView, StatusView, UploadView
from pulpcore.constants import API_ROOT

Expand Down Expand Up @@ -123,14 +122,14 @@ def __repr__(self):
url(r'^auth/', include('rest_framework.urls')),
]

api_info = openapi.Info(
title="Pulp 3 API",
default_version='v3',
)

docs_schema_view = yasg_get_schema_view(
openapi.Info(
title="Pulp3 API",
default_version='v3',
),
public=True,
permission_classes=(permissions.AllowAny,),
generator_class=PulpOpenAPISchemaGenerator,
)
urlpatterns.append(url(
r'^{api_root}docs/api(?P<format>\.json|\.yaml)'.format(api_root=API_ROOT),
Expand Down