Skip to content

Commit

Permalink
Add decorators for excluding API endpoints (#22)
Browse files Browse the repository at this point in the history
* Add @exclude_schema
* Add @exclude_schema_for
* Add @exclude_all_schemas
* Bump version to 1.2.0
  • Loading branch information
kdmccormick authored Mar 26, 2020
1 parent 60b7943 commit c2f2456
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 108 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ Change Log
Unreleased
----------

1.2.0 --- 2020-03-20
--------------------

* Added three new decorators for excluding endpoints from API documentation generation:

* ``@exclude_schema``
* ``@exclude_schema_for(method_name)``
* ``@exclude_all_schemas``


1.1.0 --- 2020-03-20
--------------------
Expand Down
88 changes: 88 additions & 0 deletions docs/excluding.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
.. _excluding:

Excluding API endpoints from documentation
==========================================

Once installed, ``edx-api-doc-tools`` will automatically generate browsable
documentation for all API endpoints within the ``/api/`` path.
This may not be what you want.

Analogous to the :func:`schema` and :func:`schema_for` decorators,
there exist the ``exclude_schema`` and :func:`exclude_schema_for` decorators,
both of which prevent the target endpoint from appearing in your API documentation.
The former is useful when your endpoint handler is defined directly in your source file,
whereas the latter is useful when the handler is implemented by a base class.

Furthermore, :func:`exclude_schema_for` can be used on a View or Viewset to
exclude multiple endpoints at once.
If you wish to exclude *all* endpoints for View or Viewset, decorate it with
``exclude_schema_for_all``.

For example::

...
from edx_api_doc_tools import exclude_schema, exclude_schema_for, exclude_schema_for_all
...
class MyViewsetWithSomeDocs(ViewSet):
def retrieve(...):
"""
This will appear in the docs.
"""

@exclude_schema
def update(...):
"""
This will NOT appear in the docs.
"""


@exclude_schema_for_all
class MyViewsetWithNoDocs(ViewSet):
def retrieve(...):
"""
This will NOT appear in the docs.
"""
def update(...):
"""
This will NOT appear in the docs.
"""


# Note that ``ModelAPIView`` comes with pre-implemented handlers for
# GET, POST, PUT, PATCH, and DESTROY.


class MyModelViewWithAllDocs(ModelAPIView):
"""
Will have docs for GET, POST, PUT, PATCH, and DESTROY.
"""

@exclude_schema_for('destroy')
class MyModelViewWithMostDocs(ModelAPIView):
"""
Will have docs for GET, POST, PUT, and PATCH.
"""

@exclude_schema_for('put', 'patch', 'destroy')
class MyModelViewWithSomeDocs(ModelAPIView)
"""
Will have docs for GET and POST.
"""

@exclude_schema_for_all
class MyViewModelViewNoDocs(ModelAPIView)
"""
ModelAPIView has handlers for GET, POST, PUT, PATCH, and DESTROY,
but we will not see any docs for this view.
"""

@exclude_schema_for_all
class MyViewWithMostDocs(APIView)
def get(self, request):
"""
This won't appear in the docs.
"""
def post(self, request):
"""
Nor will this.
"""
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Contents:

adding
writing
excluding
api
development
changelog
11 changes: 9 additions & 2 deletions edx_api_doc_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@
query_parameter,
string_parameter,
)
from .view_utils import is_schema_request, schema, schema_for
from .view_utils import (
exclude_schema,
exclude_schema_for,
exclude_schema_for_all,
is_schema_request,
schema,
schema_for,
)


__version__ = '1.1.0'
__version__ = '1.2.0'

default_app_config = 'edx_api_doc_tools.apps.EdxApiDocToolsConfig'
75 changes: 75 additions & 0 deletions edx_api_doc_tools/view_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from django.utils.decorators import method_decorator
from drf_yasg.utils import swagger_auto_schema
from rest_framework.viewsets import ViewSet

from .internal_utils import dedent, split_docstring

Expand Down Expand Up @@ -47,6 +48,61 @@ def schema_for_inner(view_class):
return schema_for_inner


def exclude_schema_for(*method_names):
"""
Decorate a class to exlcude one or more of of its methods from the API docs.
Arguments:
method_names (list[str]): Names of view methods whose operations will be
excluded from the generated API documentation.
Example::
@schema_for('get', ...)
@schema_for('delete', ...)
@exclude_schema_for('put', 'patch')
class MyView(RetrieveUpdateDestroyAPIView):
pass
"""
def exclude_schema_for_inner(view_class):
"""
Decorate a view class to exclude specified methods.
"""
for method_name in method_names:
method_decorator(
name=method_name, decorator=exclude_schema
)(view_class)
return view_class
return exclude_schema_for_inner


def exclude_schema_for_all(view_class):
"""
Decorate a class to exlcude all of its methods from the API docs.
Arguments:
view_class (type): A type, typically a subclass of View or ViewSet.
Example::
@exclude_schema_for_all
class MyView(RetrieveUpdateDestroyAPIView):
pass
"""
all_viewset_api_methods = {
'list', 'retrieve', 'create', 'update', 'partial_update', 'destroy'
}
all_view_api_methods = {
'get', 'post', 'put', 'patch', 'delete'
}
is_viewset = issubclass(view_class, ViewSet)
all_api_methods = all_viewset_api_methods if is_viewset else all_view_api_methods
methods_to_exclude = {
method for method in all_api_methods if hasattr(view_class, method)
}
return exclude_schema_for(*methods_to_exclude)(view_class)


def schema(
parameters=None,
responses=None,
Expand Down Expand Up @@ -91,6 +147,25 @@ def schema_inner(view_func):
return schema_inner


def exclude_schema(view_func):
"""
Decorate an API-endpoint-handling function to exclude it from the API docs.
Example::
class MyView(APIView):
@schema(...)
def get(...):
pass
@exclude_schema
def post(...):
pass
"""
return swagger_auto_schema(auto_schema=None)(view_func)


def is_schema_request(request):
"""
Return whether this request is serving an OpenAPI schema.
Expand Down
10 changes: 8 additions & 2 deletions example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@

from edx_api_doc_tools import make_api_info, make_docs_urls

from .views import HedgehogInfoView, HedgehogViewSet
from .views import HedgehogInfoView, HedgehogUndocumentedView, HedgehogUndocumentedViewset, HedgehogViewSet


urlpatterns = []

ROUTER = SimpleRouter()
ROUTER.register(r'api/hedgehog/v0/hogs', HedgehogViewSet, basename='hedgehog')
ROUTER.register(
r'api/hedgehog/v0/hogs', HedgehogViewSet, basename='hedgehog'
)
ROUTER.register(
r'api/hedgehog/v0/undoc-viewset', HedgehogUndocumentedViewset, basename='undoc-viewset'
)
urlpatterns += ROUTER.urls

urlpatterns += [
url(r'/api/hedgehog/v0/info', HedgehogInfoView.as_view()),
url(r'/api/hedgehog/v0/undoc-view', HedgehogUndocumentedView.as_view()),
]

urlpatterns += make_docs_urls(
Expand Down
69 changes: 50 additions & 19 deletions example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@

from rest_framework.exceptions import APIException, NotFound
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import ModelViewSet

from edx_api_doc_tools import path_parameter, query_parameter, schema, schema_for
from rest_framework.viewsets import ModelViewSet, ViewSet

from edx_api_doc_tools import (
exclude_schema,
exclude_schema_for,
exclude_schema_for_all,
path_parameter,
query_parameter,
schema,
schema_for,
)

from .data import get_hedgehogs
from .serializers import HedgehogSerializer
Expand Down Expand Up @@ -66,22 +74,8 @@
parameters=[HEDGEHOG_KEY_PARAMETER],
responses=HEDGEHOG_ERROR_RESPONSES,
)
@schema_for(
'update',
"""
Create a or modify a hedgehog.
""",
parameters=[HEDGEHOG_KEY_PARAMETER],
responses=HEDGEHOG_ERROR_RESPONSES,
)
@schema_for(
'partial_update',
"""
Modify an existing hedgehog.
""",
parameters=[HEDGEHOG_KEY_PARAMETER],
responses=HEDGEHOG_ERROR_RESPONSES,
)
# The next line will exclude the PUT and PATCH endpoints from the API docs.
@exclude_schema_for('update', 'partial_update')
@schema_for(
'destroy',
"""
Expand Down Expand Up @@ -142,6 +136,20 @@ def perform_destroy(self, instance):
raise EndpointNotImplemented()


@exclude_schema_for_all
class HedgehogUndocumentedViewset(ViewSet):
"""
A view that allows us to retrieve something.
For whatever reason, we don't want it showing up on the API docs page.
"""
def retrieve(self, request):
"""
Retrieve something or other.
"""
raise EndpointNotImplemented()


class HedgehogInfoView(GenericAPIView):
"""Information about the API."""

Expand All @@ -154,6 +162,29 @@ def get(self, request):
"""
raise EndpointNotImplemented()

@exclude_schema
def patch(self, request):
"""
Update information about the Hedgehog API.
Internal-only; this endpoint is not exposed in the docs.
"""
raise EndpointNotImplemented()


@exclude_schema_for_all
class HedgehogUndocumentedView(GenericAPIView):
"""
A view that allows us to GET something.
For whatever reason, we don't want it showing up on the API docs page.
"""
def get(self, request):
"""
Get something or other.
"""
raise EndpointNotImplemented()


class EndpointNotImplemented(APIException):
"""
Expand Down
Loading

0 comments on commit c2f2456

Please sign in to comment.