让 API 自动生成文档

2020-11-25 09:57:14 浏览数 (1)

阅读本文大概需要 7.9 分钟。

程序员最苦恼的事情莫过于写文档。由于业务口径频繁变更,因此很多接口也会频繁变更,频繁变更导致文档的维护是一件相当费时的事情,当优先级更高的事情袭来,更新文档反到成了次要工作,久而久之,文档就算有,也不是最新的,有些接口,干脆文档也不写了,口口相传了事。

没有文档,对于新手或者工作交接,是一件非常麻烦的事情,也不利于程序的传承。

那么,有没有这样一种程序,根据 api 函数的规范注释,及 api 的功能自动生成 api 的文档呢?这样一来,改接口,只要注释完善下,api 文档就自动生成,文档时刻保持最新,岂不省事。网上搜索了下,还真有大神实现了这样的框架。不得不感慨,没有程序员实现不了的好功能,只有程序员想不到的好方法。

实际上,一些流行的 web 框架已经原生集成了自动生成 api 文档的功能。比如我最近学习的 django rest framework 框架就可以自动生成 api 文档,有了这个功能,领导再也不用担心没有接口文档了。

下面对官方给和样例程序及自定义的 api 来自动生成文档,暂时不考虑 api 的权限及有选择的生成 api 文档的功能,这些在深入学习之后,都不是难事。这些样例的作用在于快速展示如何自动生成 api 文档的功能,想深入了解的还是要看下框架的源代码。

先开发 api

请先仿照 django rest framework 官方的教程快速实现一个 api。 https://www.django-rest-framework.org/tutorial/quickstart/

比如,我这里的 api 视图代码就是这样子的:

1、官方的 api

代码语言:javascript复制
##官方api

from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from mail.serializers import UserSerializer, GroupSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

2、我自己写的一个发送邮件的 api ,代码如下:

代码语言:javascript复制
# Create your views here.
from rest_framework.views import APIView
from django.core.mail import send_mail, BadHeaderError
from rest_framework.response import Response
from .get_parameter import get_parameter_dic
import logging
logger = logging.getLogger('django')
##自定义api
class SendMail(APIView):
    """
    发送信息到指定人员邮箱
    """
    def post(self, request, format=None):
        """
        post:
        发送信息到指定人员邮箱
        参数列表:
            from_email: 发件人邮箱
            to_email: 收件人,多个收件人请使用英文逗号分隔隔开
            subject: 邮件主题
            message: 邮件正文
        """
        # 获取请求方的 ip 地址
        print(request.data)
        ip = ""
        if 'HTTP_X_FORWARDED_FOR' in request.META:
            ip = request.META['HTTP_X_FORWARDED_FOR']
        else:
            ip = request.META['REMOTE_ADDR']

        params = get_parameter_dic(request)
        subject = params['subject']
        message = params['message']
        from_email = params.get('from_email', 'somenzz@163.com')
        to_email = params['to_email']

        return_data = {'code': 0, 'message': f'send email to {to_email} successful.'}
        if subject and message and to_email:
            try:
                to_email = to_email.split(',')  # 多个收件人以;分隔
                print("to_email", to_email, type(to_email))
                send_mail(subject, message, from_email, to_email)
            except BadHeaderError:
                return_data['code'] = 1
                return_data['message'] = 'Invalid header found.'
        else:
            # In reality we'd use a form class
            # to get proper validation errors.
            return_data['code'] = 2
            return_data['message'] = 'Make sure all fields are entered and valid.'
        logger.info(
            f"ip = {ip}, subject = {subject },message = {message }, from_email = {from_email }, to_email = {to_email }, return_data = {return_data }")

        return Response(return_data)

这里使用了 post 方法,在 post 请求的 body 里可以传输 4 个参数,分别是 subject 、message、from_email、to_email。其中 from_email 有默认值,是 somenzz@163.com,因此这个参数也可以省略。

这里分享下 django 框架获取参数的通用函数

django 框架获取参数有多种方式,如 get 请求中参数都会在 url 中传输,比如:http://xxx.com/api/?name=asdf&phone=13xxxx 这样。使用 request.query_params 中可以获取 name,phone 等参数,request.query_params 返回的数据类型为 QueryDict,QueryDict 转为普通 python 字典 query_params.dict()即可。

在 post 请求参数一般放在请求的 body 中, 但是仍可以放在 url 仍中,类似 get 的形式, 最终结果, 参数会有两部分组成, 一部分在 url 中, 一部分在http body 中, 但是非常不建议这样做。接下来的代码编写也不会考虑这样的情况, post 仅考虑所有参数都在 http body 中的情况。这样,无论是 post ,还是 get ,我们可以编写统一的 参数获取函数,如下所示:

代码语言:javascript复制
from django.http import QueryDict
from rest_framework.request import Request
def get_parameter_dic(request, *args, **kwargs):
    if isinstance(request, Request) == False:
        return {}

    query_params = request.query_params
    if isinstance(query_params, QueryDict):
        query_params = query_params.dict()
    result_data = request.data
    if isinstance(result_data, QueryDict):
        result_data = result_data.dict()

    if query_params != {}:
        return query_params
    else:
        return result_data

也是自定义 api 中

代码语言:javascript复制
from .get_parameter import get_parameter_dic

使用的函数。

3、修改 settings.py 在 INSTALLED_APPS 增加两项:

代码语言:javascript复制
INSTALLED_APPS = [
    'mail.apps.MailConfig', #自定义的 app
    'rest_framework', #导入 rest_framework
]

修改时区:

代码语言:javascript复制
TIME_ZONE = 'Asia/Shanghai'

增加发邮件的信息

代码语言:javascript复制
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'somezz@163.com'
EMAIL_HOST_PASSWORD = '******'
EMAIL_USE_LOCALTIME = Tru

方法一、使用原生样式自动生成 api 文档

修改项目总的 urls.py,加入文档 url 路由,如下所示:

代码语言:javascript复制
from django.contrib import admin
from django.urls import path,include
from rest_framework.documentation import include_docs_urls

from rest_framework import routers
from mail import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# Create our schema's view w/ the get_schema_view() helper method. Pass in the proper Renderers for swagger

urlpatterns = [
    path('admin/', admin.site.urls),
    path('mail/',include('mail.urls')),
    path('',include(router.urls)),
    path('api-docs/', include_docs_urls(title="api接口文档")),

]

与原本的 urls.py 相比,其实就多了两行代码,

代码语言:javascript复制
from rest_framework.documentation import include_docs_urls
path('api-docs/', include_docs_urls(title="api接口文档")),

就是这两行代码,自动生成了 api 的文档。下面来看一下效果

api1.png

我们可以看到这个 api 接口文档已经相当丰富了,左侧是所有的 api 列表,点击可以定位到相应的说明,也可以与点击 Source Code 查看多种语言调用 api 的样例代码。也可以点击 interact 按钮与 api 进行交互来测试 api,如下图所示:

api2.png

api3.png

api33.png

也可以在侧查看返回信息,及原始字符串 raw。这些 api 有个共同点就是使用 django rest framework 封装好的类来实现的,屏蔽了很多细节,现在我们看一下自定义的发邮件 api,看看它的交互如何?

自定义的api

可以看到它获取到了 api 中的注释字符串。

自定义的api 未发现参数框

我们发现自定义的 api 没有对应的参数可以填写,这真让人郁闷。仔细啃官方的英文文档,终于在第二天实现了,方法如下: 修改自定义的 api 视图类,加入以下代码:

代码语言:javascript复制
    schema = AutoSchema(manual_fields= [
        coreapi.Field(name="subject", required=True, location="query", description="邮件主题"),
        coreapi.Field(name="message", required=True, location="query", description="邮件正文"),
        coreapi.Field(name="from_email", required=False, location="query", description="发件人"),
        coreapi.Field(name="to_email", required=True, location="query", description="收件人,多个使用逗号分隔"),
    ])

前提要导入以下包:

代码语言:javascript复制
from rest_framework.schemas import AutoSchema
import coreapi

再次查看自定义的 api,发现有变化了:

自定义的api

我们发现,有了参数,但是描述信息不知道为什么没有获取到,如果有大神知道,请赐教。

下面交互,

自定义的api交互成功

终于大功告成,后面有人来问 api 的事情,不用理他,向他扔这样一个 api 文档就可以了。

注意,这里依赖 coreapi ,使用过程中使用 pip 安装下即可

代码语言:javascript复制
pip install coreapi 

方法二、使用第三方库自动生成 api 文档

这里介绍下 django-rest-swagger,使用方法如下: 1、先安装:

代码语言:javascript复制
pip install django-rest-swagger

2、加入到 INSTALLED_APPS

代码语言:javascript复制
    INSTALLED_APPS = (
        ...
        'rest_framework_swagger',
    )

3、修改项目 urls.py,类似下面这样:

代码语言:javascript复制
from django.conf.urls import url
from rest_framework_swagger.views import get_swagger_view

schema_view = get_swagger_view(title='API 接口文档')

urlpatterns = [
    url(r'^docs$', schema_view)
]

本例中的效果如下所示:

rest_framework_swagger

rest_framework_swagger

交互

交互

功能和原生的大同小异,多了 curl 访问接口的方式,每个人喜欢的风格不一样,网上还有很多生成 api 文档的轮子,大家可以选一款自己喜欢的直接用就好。

完整代码已上传至百度云,微信公众号 somenzz 回复「api」获取下载链接,欢迎一起学习交流。

0 人点赞