Python 实现 Swagger yaml 格式 api 文档合并

需求来源

公司业务系统中,API文档定义使用了Swagger,由于多人开发中每个人开发的节点不同,整体的业务过于复杂,维护一套完整的 API 文档十分繁重,且容易出现误修改,所以用 python 实现了多个 yaml 文件 OpenAPI 文档合并工具。

Open API 相关资料

推荐阅读

  1. Writing OpenAPI (Swagger) Specification Tutorial
    https://apihandyman.io/writing-openapi-swagger-specification-tutorial-part-1-introduction/

  2. Swagger 从入门到精通 (第一个链接的译文)
    https://www.gitbook.com/book/huangwenchao/swagger/details

  3. OpenAPI Specification 2.0 (手册,无需通读)
    https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md

推荐工具

  1. Prism (OpenAPI Mock 工具)
    http://stoplight.io/platform/prism/

YAML示例

swagger: "2.0"
info:
  version: "0.0.1"
  title: XXX
  description: XXX
host: localhost:4010
schemes:
  - http
basePath: /api/v1
produces:
 - application/json
paths:
    ... # 路由项
definitions:
    ... # 定义项
responses:
    ... # 响应消息中定义项
parameters:
    ... # 路径参数定义项

核心逻辑

1. 各个模块(paths,definitions等)的查找分离

def findstring(all_the_text):
    head_string = re.search(r".*paths:", all_the_text, re.S).group()
    # 删除 paths: 之上的头部信息
    all_the_text = all_the_text.replace(head_string, '\npaths:')
    # 获取模块名,以此为匹配条件
    module_name_strings = re.findall(r"(\n\w*:)", all_the_text, re.S)
    # print(module_name_strings)
    # 新建字典存放模块内容
    modules = {}
    for i in range(len(module_name_strings)):
        if i + 1 "\n","").replace(":",""))] = \
            re.search(module_name_strings[i]+".*"+module_name_strings[i+1], all_the_text, re.S).group()\
            .replace(module_name_strings[i],"").replace(module_name_strings[i+1],"")
        else:
            modules[(module_name_strings[i].replace("\n","").replace(":",""))] = \
            re.search(module_name_strings[i]+".*", all_the_text, re.S).group().replace(module_name_strings[i],"")
    # 应用平移函数
    for key in modules:
        modules[key] = remove(modules[key]) # remove 在 2.中实现
    return modules

2. 代码整体平移,解决行首空格数量不一致问题

# 平移代码
def remove(text):
    if text != '':
        # 去除代码中的注释
        text = re.sub(r'\n(\s*)#(.*)', "", text)
        spaces = re.search(r"\n\s*", text, re.S).group()
        spaces_len = len(spaces) - 1 # 计算首行 空格数
        if spaces_len != 2:
            text = text.replace('\n'+' '*spaces_len ,'\n  ')
    return text

3. 检测 API 各部分重复定义的情况,并提供自动合并(保留最后一个)

# 去除重定义
def remove_duplicate(name,text):  #name是模块名称 
    names = re.findall(r"(\n\s\s\w+:)", text, re.S)
    names_set = set(names)
    remove_module = {}
    duplicate = False
    for item in names_set:
        if names.count(item) > 1:
            print("发现重定义的%s: %s  次数:%s" %(name,item.replace("\n  ","").replace(":",""),names.count(item)))
            remove_module[item] = names.count(item)
            duplicate = True # 发现重复,进入去重逻辑
    if duplicate:
        print('是否自动合并?(Y/N)')
        flag = input()
        if (flag == 'Y') or (flag == 'y'):
            print('自动合并中...')
            for i in range(len(names)):
                if (names[i] in remove_module.keys() and remove_module[names[i]] > 1):
                    remove_string = re.search(names[i]+"(.*?)"+names[i+1], text, re.S).group()
                    if names[i+1] != names[i]:
                        remove_string = remove_string.replace(names[i+1],"")
                    else:
                        remove_string = names[i] + remove_string.replace(names[i+1],"")
                    text = text.replace(remove_string,"",1)
                    remove_module[names[i]] = remove_module[names[i]] - 1
        else:
            print('已忽略,请手工处理...')
    return text

整体代码

import re
import os

# 平移代码
def remove(text):
    if text != '':
        # 去除代码中的注释
        text = re.sub(r'\n(\s*)#(.*)', "", text)
        spaces = re.search(r"\n\s*", text, re.S).group()
        spaces_len = len(spaces) - 1 # 计算首行 空格数
        if spaces_len != 2:
            text = text.replace('\n'+' '*spaces_len ,'\n  ')
    return text

# 查找指定代码段
def findstring(all_the_text):
    head_string = re.search(r".*paths:", all_the_text, re.S).group()
    # 删除 paths: 之上的头部信息
    all_the_text = all_the_text.replace(head_string, '\npaths:')
    # print(all_the_text)
    # 获取模块名,以此为匹配条件
    module_name_strings = re.findall(r"(\n\w*:)", all_the_text, re.S)
    # print(module_name_strings)
    # 新建字典存放模块内容
    modules = {}
    for i in range(len(module_name_strings)):
        if i + 1 "\n","").replace(":",""))] = \
            re.search(module_name_strings[i]+".*"+module_name_strings[i+1], all_the_text, re.S).group()\
            .replace(module_name_strings[i],"").replace(module_name_strings[i+1],"")
        else:
            modules[(module_name_strings[i].replace("\n","").replace(":",""))] = \
            re.search(module_name_strings[i]+".*", all_the_text, re.S).group().replace(module_name_strings[i],"")
    # 应用平移函数
    for key in modules:
        modules[key] = remove(modules[key])
    return modules

# 去除重定义函数
def remove_duplicate(name,text):
    names = re.findall(r"(\n\s\s\w+:)", text, re.S)
    names_set = set(names)
    remove_module = {}
    duplicate = False
    for item in names_set:
        if names.count(item) > 1:
            print("发现重定义的%s: %s  次数:%s" %(name,item.replace("\n  ","").replace(":",""),names.count(item)))
            remove_module[item] = names.count(item)
            duplicate = True # 发现重复,进入去重
    if duplicate:
        print('是否自动合并?(Y/N)')
        flag = input()
        if (flag == 'Y') or (flag == 'y'):
            print('自动合并中...')
            for i in range(len(names)):
                if (names[i] in remove_module.keys() and remove_module[names[i]] > 1):
                    remove_string = re.search(names[i]+"(.*?)"+names[i+1], text, re.S).group()
                    if names[i+1] != names[i]:
                        remove_string = remove_string.replace(names[i+1],"")
                    else:
                        remove_string = names[i] + remove_string.replace(names[i+1],"")
                    text = text.replace(remove_string,"",1)
                    remove_module[names[i]] = remove_module[names[i]] - 1
        else:
            print('已忽略,请手工处理...')
    return text

# 获取文件列表
def GetFileList(dir, fileList):
    newDir = dir
    if os.path.isfile(dir):
        fileList.append(dir)
    elif os.path.isdir(dir):
        for s in os.listdir(dir):
            #如果需要忽略某些文件夹,使用以下代码
            if not (".yaml" in s) or ("api-all" in s) or ("api-combine" in s):
                continue
            newDir=os.path.join(dir,s)
            GetFileList(newDir, fileList)
    return fileList

# Main 代码
string_all = {}
string_all['paths'] = ''
string_all['definitions'] = ''
string_all['responses'] = ''
string_all['parameters'] = ''

file_names = GetFileList(os.getcwd(), [])
print("检测到当前目录以下yaml文件:")
for e in file_names:
    print(e)
print('共 %s 个文件需合并'%(len(file_names)))
print('\n正在合并中...\n')
# 循环读取文件
for file_name in file_names:
    file_object = open(file_name,'r',encoding= 'utf8')
    try:
         all_the_text = file_object.read()
    finally:
         file_object.close()
    text_modules = findstring(all_the_text)
    if 'paths' in text_modules:
        string_all['paths'] += text_modules['paths']
    if 'definitions' in text_modules:
        string_all['definitions'] += text_modules['definitions']
    if 'responses' in text_modules:
        string_all['responses'] += text_modules['responses']
    if 'parameters' in text_modules:
        string_all['parameters'] += text_modules['parameters']

# 去重
for key in string_all:
    string_all[key] = remove_duplicate(key,string_all[key])

module = '''swagger: '2.0'
info:
  title: XXX
  description:  XXX
  version: "1.0.0"
host: localhost:4010
schemes:
  - http
basePath: /api/v1
produces:
  - application/json
paths:%s
definitions:%s
responses:%s
parameters:%s
''' % (string_all['paths'], string_all['definitions'], string_all['responses'], string_all['parameters'])

# parameters 不存在时,去掉 parameters:
if string_all['parameters'] == '':
    module = module.replace('\nparameters:','')

# 去除多余空行
module = re.sub(r"\n\s*\n", "\n", module)

result_file = open('api-combine.yaml','w+',encoding= 'utf8')
result_file.write(module)
print('api-combine.yaml生成成功!!!');
input()

使用方式

将代码放到单独的 .py 文件中,置于 yaml API 文档文件夹目录下执行即可,需安装Python3.x。

小尾巴

笔者的python并不精通,O(∩_∩)O~ ,代码仍需改进,可能存在各种bug,仅供参考!

你可能感兴趣的:(笔记,python,api,文档,合并)