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

来源:互联网 发布:知乎用户数量一亿 编辑:程序博客网 时间:2024/06/07 05:58

需求来源

公司业务系统中,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: XXXhost: localhost:4010schemes:  - httpbasePath: /api/v1produces: - application/jsonpaths:    ... # 路由项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 <len(module_name_strings):            modules[(module_name_strings[i].replace("\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 reimport 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 <len(module_name_strings):            modules[(module_name_strings[i].replace("\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:4010schemes:  - httpbasePath: /api/v1produces:  - application/jsonpaths:%sdefinitions:%sresponses:%sparameters:%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,仅供参考!

0 0
原创粉丝点击