From 2b06a941b4aafe7b58fb5f400b5f5dd3bc3e3039 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 12 Dec 2019 11:12:58 -0500 Subject: [PATCH 1/3] Use edx/api-doc-tools for api doc support --- requirements/edx/base.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/base.in b/requirements/edx/base.in index 293cae188d..22378e5fb0 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -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 From 7d7d6a655a5c8f89a541b47513623b83759ae085 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 12 Dec 2019 13:36:06 -0500 Subject: [PATCH 2/3] make upgrade --- requirements/edx/base.txt | 12 +++--------- requirements/edx/development.txt | 1 + requirements/edx/testing.txt | 15 ++++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index f322c4f1d7..c8f8503ddf 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -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 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index a06ac7bdb6..97be90f6dc 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -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 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index b281b26c48..fdeada5188 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -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 From 079d17b8998a03b1a7f17add89c5da44ef109434 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 12 Dec 2019 16:54:37 -0500 Subject: [PATCH 3/3] Remove code that is now in edx_api_doc_tools --- Makefile | 2 +- cms/urls.py | 17 +- docs/swagger.yaml | 70 +------- lms/djangoapps/certificates/apis/v0/views.py | 3 +- lms/envs/common.py | 2 +- lms/urls.py | 17 +- openedx/core/apidocs.py | 156 +----------------- .../core/djangoapps/bookmarks/serializers.py | 4 +- openedx/core/djangoapps/bookmarks/views.py | 2 +- 9 files changed, 19 insertions(+), 254 deletions(-) diff --git a/Makefile b/Makefile index 7e6bb596e4..9f16d0018b 100644 --- a/Makefile +++ b/Makefile @@ -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/* diff --git a/cms/urls.py b/cms/urls.py index 57633bcb9f..6326e953d7 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -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\.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 += [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 301b387b3c..b02947c814 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -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 diff --git a/lms/djangoapps/certificates/apis/v0/views.py b/lms/djangoapps/certificates/apis/v0/views.py index 26c3825344..4479086ac3 100644 --- a/lms/djangoapps/certificates/apis/v0/views.py +++ b/lms/djangoapps/certificates/apis/v0/views.py @@ -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__) diff --git a/lms/envs/common.py b/lms/envs/common.py index 46db4c7237..a7b9c8fd8f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -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. diff --git a/lms/urls.py b/lms/urls.py index c8b2dfd15b..ac9217f235 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -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\.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 += [ diff --git a/openedx/core/apidocs.py b/openedx/core/apidocs.py index 6f2350c3d0..9f81cfa591 100644 --- a/openedx/core/apidocs.py +++ b/openedx/core/apidocs.py @@ -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,), -) diff --git a/openedx/core/djangoapps/bookmarks/serializers.py b/openedx/core/djangoapps/bookmarks/serializers.py index 45721b2f30..4100cf69ca 100644 --- a/openedx/core/djangoapps/bookmarks/serializers.py +++ b/openedx/core/djangoapps/bookmarks/serializers.py @@ -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 diff --git a/openedx/core/djangoapps/bookmarks/views.py b/openedx/core/djangoapps/bookmarks/views.py index a0b44b3258..8f4fb3978c 100644 --- a/openedx/core/djangoapps/bookmarks/views.py +++ b/openedx/core/djangoapps/bookmarks/views.py @@ -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