Merge pull request #22547 from edx/nedbat/api-docs-from-lib

Use API doc tooling from its own library
This commit is contained in:
Ned Batchelder
2020-01-21 11:39:21 -05:00
committed by GitHub
13 changed files with 32 additions and 271 deletions

View File

@@ -24,7 +24,7 @@ SWAGGER = docs/swagger.yaml
docs: api-docs guides ## build all the developer documentation for this repository
swagger: ## generate the swagger.yaml file
DJANGO_SETTINGS_MODULE=docs.docs_settings python manage.py lms generate_swagger --generator-class=openedx.core.apidocs.ApiSchemaGenerator -o $(SWAGGER)
DJANGO_SETTINGS_MODULE=docs.docs_settings python manage.py lms generate_swagger --generator-class=edx_api_doc_tools.ApiSchemaGenerator -o $(SWAGGER)
api-docs-sphinx: swagger ## generate the sphinx source files for api-docs
rm -f docs/api/gen/*

View File

@@ -2,12 +2,12 @@
Urls of Studio.
"""
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.contrib.admin import autodiscover as django_autodiscover
from django.utils.translation import ugettext_lazy as _
from edx_api_doc_tools import make_docs_urls
from ratelimitbackend import admin
import contentstore.views
@@ -17,7 +17,7 @@ import openedx.core.djangoapps.lang_pref.views
from cms.djangoapps.contentstore.views.organization import OrganizationListView
from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance
from openedx.core.djangoapps.password_policy.forms import PasswordPolicyAwareAdminAuthForm
from openedx.core.apidocs import schema_view
from openedx.core.apidocs import api_info
django_autodiscover()
@@ -280,18 +280,7 @@ urlpatterns += [
]
# API docs.
urlpatterns += [
url(
r'^swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=settings.OPENAPI_CACHE_TIMEOUT), name='schema-json',
),
url(
r'^swagger/$',
schema_view.with_ui('swagger', cache_timeout=settings.OPENAPI_CACHE_TIMEOUT),
name='schema-swagger-ui',
),
url(r'^api-docs/$', schema_view.with_ui('swagger', cache_timeout=settings.OPENAPI_CACHE_TIMEOUT)),
]
urlpatterns += make_docs_urls(api_info)
if 'openedx.testing.coverage_context_listener' in settings.INSTALLED_APPS:
urlpatterns += [

View File

@@ -1997,7 +1997,7 @@ paths:
/edx_proctoring/v1/proctored_exam/active_exams_for_user:
get:
operationId: edx_proctoring_v1_proctored_exam_active_exams_for_user_list
description: returns the get_active_exams_for_user
description: Returns the get_active_exams_for_user
parameters: []
responses:
'200':
@@ -4615,73 +4615,6 @@ paths:
in: path
required: true
type: string
/xblock/v2/xblocks/{usage_key_str}/handler/{user_id}-{secure_token}/{handler_name}/{suffix}:
get:
operationId: xblock_v2_xblocks_handler_read
description: Run an XBlock's handler and return the result
parameters: []
responses:
'200':
description: ''
tags:
- xblock
post:
operationId: xblock_v2_xblocks_handler_create
description: Run an XBlock's handler and return the result
parameters: []
responses:
'201':
description: ''
tags:
- xblock
put:
operationId: xblock_v2_xblocks_handler_update
description: Run an XBlock's handler and return the result
parameters: []
responses:
'200':
description: ''
tags:
- xblock
patch:
operationId: xblock_v2_xblocks_handler_partial_update
description: Run an XBlock's handler and return the result
parameters: []
responses:
'200':
description: ''
tags:
- xblock
delete:
operationId: xblock_v2_xblocks_handler_delete
description: Run an XBlock's handler and return the result
parameters: []
responses:
'204':
description: ''
tags:
- xblock
parameters:
- name: handler_name
in: path
required: true
type: string
- name: secure_token
in: path
required: true
type: string
- name: suffix
in: path
required: true
type: string
- name: usage_key_str
in: path
required: true
type: string
- name: user_id
in: path
required: true
type: string
/xblock/v2/xblocks/{usage_key_str}/handler_url/{handler_name}/:
get:
operationId: xblock_v2_xblocks_handler_url_read
@@ -5387,7 +5320,6 @@ definitions:
title: Order number
type: string
maxLength: 128
minLength: 1
x-nullable: true
support_details:
title: Support details

View File

@@ -5,9 +5,11 @@ import logging
import six
from django.contrib.auth import get_user_model
import edx_api_doc_tools as apidocs
from edx_rest_framework_extensions import permissions
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_condition import C
@@ -20,7 +22,6 @@ from openedx.core.djangoapps.certificates.api import certificates_viewable_for_c
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.user_api.accounts.api import visible_fields
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
from openedx.core import apidocs
log = logging.getLogger(__name__)

View File

@@ -2535,7 +2535,7 @@ REST_FRAMEWORK = {
}
SWAGGER_SETTINGS = {
'DEFAULT_INFO': 'openedx.core.apidocs.default_info',
'DEFAULT_INFO': 'openedx.core.apidocs.api_info',
}
# How long to cache OpenAPI schemas and UI, in seconds.

View File

@@ -12,6 +12,8 @@ from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import RedirectView
from ratelimitbackend import admin
from edx_api_doc_tools import make_docs_urls
from branding import views as branding_views
from lms.djangoapps.courseware.masquerade import handle_ajax as courseware_masquerade_handle_ajax
from lms.djangoapps.courseware.module_render import (
@@ -44,7 +46,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.verified_track_content import views as verified_track_content_views
from openedx.core.apidocs import schema_view
from openedx.core.apidocs import api_info
from openedx.features.enterprise_support.api import enterprise_enabled
from static_template_view import views as static_template_view_views
from staticbook import views as staticbook_views
@@ -959,18 +961,7 @@ if settings.BRANCH_IO_KEY:
]
# API docs.
urlpatterns += [
url(
r'^swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=settings.OPENAPI_CACHE_TIMEOUT), name='schema-json',
),
url(
r'^swagger/$',
schema_view.with_ui('swagger', cache_timeout=settings.OPENAPI_CACHE_TIMEOUT),
name='schema-swagger-ui',
),
url(r'^api-docs/$', schema_view.with_ui('swagger', cache_timeout=settings.OPENAPI_CACHE_TIMEOUT)),
]
urlpatterns += make_docs_urls(api_info)
# edx-drf-extensions csrf app
urlpatterns += [

View File

@@ -2,161 +2,13 @@
Open API support.
"""
import textwrap
from edx_api_doc_tools import make_api_info
from drf_yasg import openapi
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.utils import swagger_auto_schema as drf_swagger_auto_schema
from drf_yasg.views import get_schema_view
from rest_framework import permissions
# -- Code that will eventually be in another openapi-helpers repo -------------
class ApiSchemaGenerator(OpenAPISchemaGenerator):
"""A schema generator for /api/*
Only includes endpoints in the /api/* url tree, and sets the path prefix
appropriately.
"""
def get_endpoints(self, request):
endpoints = super(ApiSchemaGenerator, self).get_endpoints(request)
subpoints = {p: v for p, v in endpoints.items() if p.startswith("/api/")}
return subpoints
def determine_path_prefix(self, paths):
return "/api/"
def dedent(text):
"""
Dedent multi-line text nicely.
An initial empty line is ignored so that triple-quoted strings don't need
to start with a backslash.
"""
if "\n" in text:
first, rest = text.split("\n", 1)
if not first.strip():
# First line is blank, discard it.
text = rest
return textwrap.dedent(text)
def schema(parameters=None):
"""
Decorator for documenting an API endpoint.
The operation summary and description are taken from the function docstring. All
description fields should be in Markdown and will be automatically dedented.
Args:
parameters (Parameter list): each object may be conveniently defined with the
`parameter` function.
This is heavily inspired from the the `drf_yasg.utils.swagger_auto_schema`__
decorator, but callers do not need to know about this abstraction.
__ https://drf-yasg.readthedocs.io/en/stable/drf_yasg.html#drf_yasg.utils.swagger_auto_schema
"""
for param in parameters or ():
param.description = dedent(param.description)
def decorator(view_func):
"""
Final view decorator.
"""
operation_summary = None
operation_description = None
if view_func.__doc__ is not None:
doc_lines = view_func.__doc__.strip().split("\n")
if doc_lines:
operation_summary = doc_lines[0].strip()
if len(doc_lines) > 1:
operation_description = dedent("\n".join(doc_lines[1:]))
return drf_swagger_auto_schema(
manual_parameters=parameters,
operation_summary=operation_summary,
operation_description=operation_description
)(view_func)
return decorator
def is_schema_request(request):
"""Is this request serving an OpenAPI schema?"""
return request.query_params.get('format') == 'openapi'
class ParameterLocation(object):
"""Location of API parameter in request."""
BODY = openapi.IN_BODY
PATH = openapi.IN_PATH
QUERY = openapi.IN_QUERY
FORM = openapi.IN_FORM
HEADER = openapi.IN_HEADER
def string_parameter(name, in_, description=None):
"""
Convenient function for defining a string parameter.
Args:
name (str)
in_ (ParameterLocation attribute)
description (str)
"""
return parameter(name, in_, str, description=description)
def parameter(name, in_, param_type, description=None):
"""
Define typed parameters.
Args:
name (str)
in_ (ParameterLocation attribute)
type (type): one of object, str, float, int, bool, list, file.
description (str)
"""
openapi_type = None
if param_type is object:
openapi_type = openapi.TYPE_OBJECT
elif param_type is str:
openapi_type = openapi.TYPE_STRING
elif param_type is float:
openapi_type = openapi.TYPE_NUMBER
elif param_type is int:
openapi_type = openapi.TYPE_INTEGER
elif param_type is bool:
openapi_type = openapi.TYPE_BOOLEAN
elif param_type is list:
openapi_type = openapi.TYPE_ARRAY
elif param_type is file:
openapi_type = openapi.TYPE_FILE
else:
raise ValueError(u"Unsupported parameter type: '{}'".format(type))
return openapi.Parameter(
name,
in_,
type=openapi_type,
description=description
)
# -----------------------------------------------------
default_info = openapi.Info(
api_info = make_api_info(
title="Open edX API",
default_version="v1",
version="v1",
description="APIs for access to Open edX information",
#terms_of_service="https://www.google.com/policies/terms/", # TODO: Do we have these?
contact=openapi.Contact(email="oscm@edx.org"),
email="oscm@edx.org",
#license=openapi.License(name="BSD License"), # TODO: What does this mean?
)
schema_view = get_schema_view(
default_info,
generator_class=ApiSchemaGenerator,
public=True,
permission_classes=(permissions.AllowAny,),
)

View File

@@ -3,11 +3,11 @@ Serializers for Bookmarks.
"""
import six
from edx_api_doc_tools import is_schema_request
from rest_framework import serializers
import six
from openedx.core.lib.api.serializers import CourseKeyField, UsageKeyField
from openedx.core.apidocs import is_schema_request
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS

View File

@@ -13,6 +13,7 @@ from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
import edx_api_doc_tools as apidocs
from edx_rest_framework_extensions.paginators import DefaultPagination
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
@@ -26,7 +27,6 @@ from rest_framework_oauth.authentication import OAuth2Authentication
from openedx.core.djangoapps.bookmarks.api import BookmarksLimitReachedError
from openedx.core.lib.api.permissions import IsUserInUrl
from openedx.core.lib.url_utils import unquote_slashes
from openedx.core import apidocs
from xmodule.modulestore.exceptions import ItemNotFoundError
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS, api

View File

@@ -66,9 +66,9 @@ django-waffle==0.18.0
django-webpack-loader # Used to wire webpack bundles into the django asset pipeline
djangorestframework==3.9.4
djangorestframework-jwt
drf-yasg # Replacement for django-rest-swagger
edx-ace
edx-analytics-data-api-client
edx-api-doc-tools
edx-bulk-grades # LMS REST API for managing bulk grading operations
edx-ccx-keys
edx-celeryutils

View File

@@ -44,8 +44,6 @@ git+https://github.com/edx/openedx-chem.git@ff4e3a03d3c7610e47a9af08eb648d8aabe2
click==7.0 # via code-annotations, user-util
code-annotations==0.3.3 # via edx-enterprise
contextlib2==0.6.0.post1
coreapi==2.3.3 # via drf-yasg
coreschema==0.0.4 # via coreapi, drf-yasg
git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb8eb28e467#egg=crowdsourcehinter-xblock==0.2
cryptography==2.8
cssutils==1.0.2 # via pynliner
@@ -94,9 +92,10 @@ djangorestframework-xml==1.4.0 # via edx-enterprise
djangorestframework==3.9.4
docopt==0.6.2
docutils==0.16 # via botocore
drf-yasg==1.16
drf-yasg==1.16 # via edx-api-doc-tools
edx-ace==0.1.13
edx-analytics-data-api-client==0.15.3
edx-api-doc-tools==1.0.2
edx-bulk-grades==0.6.6
edx-ccx-keys==1.0.0
edx-celeryutils==0.3.1
@@ -136,11 +135,9 @@ help-tokens==1.0.5
html5lib==1.0.1
httplib2==0.16.0
idna==2.8
inflection==0.3.1 # via drf-yasg
ipaddress==1.0.23
isodate==0.6.0 # via python3-saml
itypes==1.1.0 # via coreapi
jinja2==2.10.3 # via code-annotations, coreschema
jinja2==2.10.3 # via code-annotations
jmespath==0.9.4 # via boto3, botocore
jsondiff==1.2.0 # via edx-enterprise
jsonfield==2.0.2
@@ -210,8 +207,6 @@ requests-oauthlib==1.1.0
requests==2.22.0
rest-condition==1.0.3
rfc6266-parser==0.0.6
ruamel.yaml.clib==0.2.0 # via ruamel.yaml
ruamel.yaml==0.16.5 # via drf-yasg
rules==2.2
s3transfer==0.1.13 # via boto3
sailthru-client==2.2.3
@@ -234,7 +229,6 @@ sympy==1.5.1
testfixtures==6.10.3 # via edx-enterprise
text-unidecode==1.3 # via python-slugify
unicodecsv==0.14.1
uritemplate==3.0.1 # via coreapi, drf-yasg
urllib3==1.25.7
user-util==0.1.5
voluptuous==0.11.7

View File

@@ -108,6 +108,7 @@ docutils==0.16
drf-yasg==1.16
edx-ace==0.1.13
edx-analytics-data-api-client==0.15.3
edx-api-doc-tools==1.0.2
edx-bulk-grades==0.6.6
edx-ccx-keys==1.0.0
edx-celeryutils==0.3.1

View File

@@ -49,8 +49,8 @@ click==7.0
code-annotations==0.3.3
colorama==0.4.3 # via radon
contextlib2==0.6.0.post1
coreapi==2.3.3
coreschema==0.0.4
coreapi==2.3.3 # via drf-yasg
coreschema==0.0.4 # via coreapi, drf-yasg
coverage==5.0.3
git+https://github.com/nedbat/coverage_pytest_plugin.git@29de030251471e200ff255eb9e549218cd60e872#egg=coverage_pytest_plugin==0.0
git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb8eb28e467#egg=crowdsourcehinter-xblock==0.2
@@ -105,6 +105,7 @@ docutils==0.16
drf-yasg==1.16
edx-ace==0.1.13
edx-analytics-data-api-client==0.15.3
edx-api-doc-tools==1.0.2
edx-bulk-grades==0.6.6
edx-ccx-keys==1.0.0
edx-celeryutils==0.3.1
@@ -156,11 +157,11 @@ httpretty==0.9.7
idna==2.8
importlib-metadata==1.4.0
inflect==3.0.2
inflection==0.3.1
inflection==0.3.1 # via drf-yasg
ipaddress==1.0.23
isodate==0.6.0
isort==4.3.21
itypes==1.1.0
itypes==1.1.0 # via coreapi
jinja2-pluralize==0.3.0
jinja2==2.10.3
jmespath==0.9.4
@@ -258,8 +259,8 @@ requests-oauthlib==1.1.0
requests==2.22.0
rest-condition==1.0.3
rfc6266-parser==0.0.6
ruamel.yaml.clib==0.2.0
ruamel.yaml==0.16.5
ruamel.yaml.clib==0.2.0 # via ruamel.yaml
ruamel.yaml==0.16.5 # via drf-yasg
rules==2.2
s3transfer==0.1.13
sailthru-client==2.2.3
@@ -289,7 +290,7 @@ tox==3.14.3
transifex-client==0.13.4
unicodecsv==0.14.1
unidiff==0.5.5
uritemplate==3.0.1
uritemplate==3.0.1 # via coreapi, drf-yasg
urllib3==1.25.7
user-util==0.1.5
virtualenv==16.7.9 # via tox