ff1b4142 by Gruel

swagger

1 parent f7cba4a5
......@@ -28,6 +28,7 @@ sftp-config.json
*.sqlite3
conf/*
docs/*
# 脚本
src/*.sh
\ No newline at end of file
......
from django.shortcuts import render
from webargs import fields
from webargs import fields, validate
from webargs.djangoparser import use_args, parser
from common.mixins import GenericView
from common import response
......@@ -14,22 +14,22 @@ def load_data(request, schema):
return request.data
application_data_args = {'applicationId': fields.Str(required=True)}
application_data_args = {'applicationId': fields.Str(required=True, validate=validate.Length(max=64))}
applicant_data_args = {
'mainApplicantName': fields.Str(required=True),
'coApplicantName': fields.Str(required=True),
'guarantor1Name': fields.Str(required=True),
'guarantor2Name': fields.Str(required=True),
'mainApplicantName': fields.Str(required=True, validate=validate.Length(max=16)),
'coApplicantName': fields.Str(required=True, validate=validate.Length(max=16)),
'guarantor1Name': fields.Str(required=True, validate=validate.Length(max=16)),
'guarantor2Name': fields.Str(required=True, validate=validate.Length(max=16)),
}
document_args = {
'documentName': fields.Str(required=True),
'documentScheme': fields.Str(required=True),
'businessType': fields.Str(required=True),
'documentName': fields.Str(required=True, validate=validate.Length(max=255)),
'documentScheme': fields.Str(required=True, validate=validate.Length(max=64)),
'businessType': fields.Str(required=True, validate=validate.Length(max=64)),
'uploadFinishTime': fields.DateTime(required=True),
'dataSource': fields.Str(required=True),
'metadataVersionId': fields.Str(required=True),
'dataSource': fields.Str(required=True, validate=validate.Length(max=64)),
'metadataVersionId': fields.Str(required=True, validate=validate.Length(max=64)),
}
doc_upload_args = {
......@@ -63,3 +63,80 @@ class DocView(GenericView):
)
self.running_log.info('[doc upload success] [args={0}]'.format(args))
return response.ok()
post.openapi_doc = '''
summary: pos上传文件信息
tags: [doc]
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
applicationData:
description: 申请信息
type: object
properties:
applicationId:
description: 申请id
type: string
example: CH-B0011010101
applicantData:
description: 申请人信息
type: object
properties:
mainApplicantName:
description: 主申请人
type: string
example: 王明阳
coApplicantName:
description: 共同申请人
type: string
example: 王明月
guarantor1Name:
description: 担保人1
type: string
example: 王明日
guarantor2Name:
description: 担保人2
type: string
example: 王明雨
document:
description: 文件信息
type: object
properties:
documentName:
description: 文件名
type: string
example: CH-B0011010101王明阳申请表
documentScheme:
description: 文件格式?
type: string
example: CO00001
businessType:
description: 业务类型
type: string
example: HIL
uploadFinishTime:
description: 上传完成时间
type: string
example: '2020-09-01 12:21:11'
dataSource:
description: 数据源
type: string
example: POS
metadataVersionId:
description: 元数据版本ID
type: string
example: '8410480'
responses:
200:
description: ok
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/ApiResponse'
'''
......
base_part = '''
openapi: 3.0.0
info:
title: 接口文档
description: 宝马ocr/biz_logic接口文档
version: 1.0.0
servers:
- url: 'http://127.0.0.1:8000'
description: Development server
- url: 'http://127.0.0.1:8000'
description: sit
- url: 'http://127.0.0.1:8000'
description: uat
- url: 'http://127.0.0.1:8000'
description: prd
tags:
- name: user
description: 用户
- name: doc
description: 文件
security:
- bearerAuth: []
'''
# scheme: bearer
security_schemes = '''
bearerAuth: # arbitrary name for the security scheme
type: http
scheme: bearer
bearerFormat: JWT
description: >
token过期标志
1. Response Headers中的WWW-Authenticate中的status
status: -1 Invalid Authorization header
status: -2 Signature has expired beacause of expire_time
status: -3 Error decoding signature
status: -4 Signature has expired beacause new signature is generated
status: 0 valid Authorization
status: 1 new create, only login would set
2. Response Body中的meta -> status == 1
'''
responses = '''
ErrorResponse:
description: 调用异常, 具体情况请参考`HTTP`状态码和`meta -> status`字段
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
NoContent:
description: 后台接收请求,但是没有响应内容
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
'''
parameters = ''
schemas = '''
ApiResponse:
description: 响应对象,meta字段用于表示响应的状态; data字段用于存放响应内容
type: object
properties:
code:
type: integer
description: '0: success
1: need login
2: invalid params
3: internal error
4: object not exist
5: async wait
6: no permission
7: illegal operation'
msg:
type: string
nullable: true
data:
oneOf:
- type: object
nullable: true
- type: integer
nullable: true
- type: array
nullable: true
Pagination:
description: 分页参数
type: object
properties:
current:
description: 当前页面
type: integer
total:
description: 所有元素个数
type: integer
page_size:
description: 页面大小
type: integer
required:
- current
- total
- pageSize
'''
\ No newline at end of file
import collections
import re
from django.core.management import BaseCommand
from django.urls.resolvers import get_resolver
import yaml
from yaml.scanner import ScannerError
from common.api_doc import (base_part, security_schemes, responses, schemas)
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
def dict_constructor(loader, node):
return collections.OrderedDict(loader.construct_pairs(node))
yaml.add_representer(collections.OrderedDict, dict_representer)
yaml.add_constructor(_mapping_tag, dict_constructor)
def unify_url_path_format(string):
return '/%s' % re.sub(r'%\(([^/]+)\)s', lambda m: '{%s}' % m.group(1),
string)
DEFAULT_API_DOC = '''
summary: 未填写
responses:
200:
description: ok
'''
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-o', '--output_file', help='文件名,用于存储文档')
def handle(self, *args, **kwargs):
urls = get_resolver()
api_doc_dct = {}
for view, pattern in urls.reverse_dict.items():
view_class = view.view_class
url_path, path_parameters = pattern[0][0]
url_path = unify_url_path_format(url_path)
url_path_paramters = getattr(view, 'parameters_doc', None)
if url_path_paramters:
url_path_paramters = yaml.load(url_path_paramters)
else:
url_path_paramters = [{
'in': 'path',
'name': parameter,
'required': True,
'schema': {
'type': 'string'
}
} for parameter in path_parameters]
api_doc_dct[url_path] = {}
if url_path_paramters:
api_doc_dct[url_path]['parameters'] = url_path_paramters
for method in view_class.http_method_names:
method_handler = getattr(view_class, method, None)
doc = getattr(method_handler, 'openapi_doc', None)
if not method_handler or (method == 'options' and not doc):
continue
try:
doc = yaml.load(doc or DEFAULT_API_DOC)
except ScannerError as err:
raise Exception(
'failed to load doc: """%s"""\nerr: %s' % (doc, err))
if doc.get('parameters'):
for parameter in doc['parameters']:
if parameter['name'] in path_parameters:
doc['parameters'].pop(parameter)
api_doc_dct[url_path][method] = doc
doc_dct = yaml.load(base_part)
doc_dct['paths'] = api_doc_dct
doc_dct['components'] = {
'securitySchemes': yaml.load(security_schemes),
'responses': yaml.load(responses),
'schemas': yaml.load(schemas),
}
doc_str = yaml.dump(
doc_dct, default_flow_style=False, allow_unicode=True)
if kwargs.get('output_file'):
with open(kwargs['output_file'], 'w') as f:
f.write(doc_str)
self.stdout.write(
self.style.SUCCESS('api doc generated succssfully: %s' %
kwargs['output_file']))
else:
self.stdout.write(doc_str)
import os
import sys
from django.core.management import BaseCommand
from openapi_spec_validator import validate_spec_url, validate_v2_spec_url
from openapi_spec_validator.exceptions import ValidationError
# from openapi_spec_validator/__main__.py
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'filename', help="Absolute or relative path to file")
parser.add_argument(
'--schema',
help="OpenAPI schema (default: 3.0.0)",
type=str,
choices=['2.0', '3.0.0'],
default='3.0.0')
def handle(self, *args, **kwargs):
filename = kwargs['filename']
filename = os.path.abspath(filename)
# choose the validator
if kwargs['schema'] == '2.0':
validate_url = validate_v2_spec_url
elif kwargs['schema'] == '3.0.0':
validate_url = validate_spec_url
# validate
try:
validate_url('file://' + filename)
except ValidationError as err:
self.stdout.write(self.style.ERROR(err))
sys.exit(1)
except Exception as err:
self.stdout.write(self.style.ERROR(err))
sys.exit(2)
else:
self.stdout.write(self.style.SUCCESS('ok'))
......@@ -4,9 +4,10 @@ from .named_enum import NamedEnum
def res_content(meta_status, msg, data=None):
if data is None:
data = {}
return {'code': meta_status, 'message': msg, 'data': data}
res = {'code': meta_status, 'message': msg}
if data is not None:
res['data'] = data
return res
@enum.unique
......
# 录题系统开发规范
## 代码规范
1. 参考 [python_specification.md](python_specification.md)
2. 帐号、密码严禁提交到git仓中
3. 参与开发前请阅读[十二要素应用宣言](https://12factor.net/zh_cn/)
## 接口文档
接口文档使用`openapi`规范书写, 可以参考[demo](http://editor.swagger.io/)以获取直观感受。
目前,接口文档写在代码中, 并通过脚本将代码中的文档提取出来, 示例如下。
1. 编写文档
```python
class UserInfoView(APIView):
def get(self, request):
user_info = serializers.staff_detail.dump(request.user).data
return APIResponse.ok(user_info)
get.openapi_doc = '''
summary: 获取个人信息
tags:
- 帐号
responses:
200:
description: ok
content:
application/json:
schema:
type: object
properties:
meta:
$ref: '#/components/schemas/ApiResponseMeta'
data:
$ref: '#/components/schemas/AccountInfoDetail'
'''
```
2. 提取接口文档
```
$: python manage.py generate_api_doc -o ../docs/main.yaml
api doc generated succssfully: ../docs/main.yaml
```
3. 校对接口文档
```
$: python manage.py validate_api_doc ../docs/main.yaml
ok
```
4. 查看接口文档
* 方法1: 粘贴到[online editor](http://editor.swagger.io/)中查看
* 方法2: 安装 https://bitbucket.org/abrahamL/openapi_toolset.git
```
$: cd docs
$: ls
main.yaml
$: python -m openapi_toolset
* Serving Flask app "openapi_toolset" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:9090/ (Press CTRL+C to quit)
```
浏览器访问http://127.0.0.1:9090即可查看文档
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!