随着业务的飞速发展,API接口愈来愈多,路由管理文件从几十号变成几百上千行,且每次上新服务,须要在修改路由文件代码,带来必定的风险。 python
下面这套规则只是其中一种方案,能够针对项目状况制定对应的规则,而后实现相关代码,可是总体思路基本同样。git
项目的API接口代码均放在resources文件夹下,且此文件夹只能写接口API服务代码。github
接口名称命名以_链接单词,而对应文件里的类名文件名称的单词,不过换成是驼峰写法。web
类的导入则经过文件名对应到类名,实现自动映射注册到web框架中。flask
规则举例以下: 如上图,resources下有一个hello_world接口,还有一个ab项目文件夹,ab下面还有一个hello_world_python接口以及子项目文件夹testab,testab下面也有一个hello_world_python.api
接口文件的文件名命名规范: 文件名命名均为小写,多个单词之间使用'_'隔开,好比hello_world.py 命名正确,helloWorld.py命名错误。数组
接口文件里的接口类Class命名是以文件名字转为驼峰格式,且首字母大写。好比hello_world.py 对应的接口类是 HelloWorld 举例: hello_world.py bash
路由入口文件会自动映射,映射规则为: 前缀 / 项目文件夹[...] / 文件名app
其中 前缀为整个项目的路由前缀,能够定义,也能够不定义,好比api-ab项目,能够定义整个项目的路由前缀为 ab/ resource下面项目文件夹若是有,则会自动拼接,若是没有,则不会读取。 举例: 前缀为空,上图resources中的三个接口对应的路由为:框架
hello_world.py ==> /hello_world
ab/hello_world_python.py ==> /ab/hello_world_python
ab/testab/hello_world_python.py ==> /ab/testab/hello_world_python
复制代码
前缀为ab/,上图resources中的三个接口对应的路由为:
hello_world.py ==> ab/hello_world
ab/hello_world_python.py ==> ab/ab/hello_world_python
ab/testab/hello_world_python.py ==> ab/ab/testab/hello_world_python
复制代码
关于resources里目录结构,代码里是能够容许N层,但建议不要超过3层, 不易管理。
python不少框架的启动和路由管理都很相似,因此这套规则适合不少框架,测试过程当中有包括flask, tornado, sanic, japronto。 之前年代久远的web.py也是支持的。
完整代码地址: github.com/CrystalSkyZ…
实现下划线命名 转 驼峰命名 函数,代码演示:
def underline_to_hump(underline_str):
''' 下划线形式字符串转成驼峰形式,首字母大写 '''
sub = re.sub(r'(_\w)', lambda x: x.group(1)[1].upper(), underline_str)
if len(sub) > 1:
return sub[0].upper() + sub[1:]
return sub
复制代码
实现根据字符串导入模块函数, 代码演示:
__import__
函数实现加载类def import_object(name):
"""Imports an object by name. import_object('x') is equivalent to 'import x'. import_object('x.y.z') is equivalent to 'from x.y import z'. """
if not isinstance(name, str):
name = name.encode('utf-8')
if name.count('.') == 0:
return __import__(name, None, None)
parts = name.split('.')
obj = __import__('.'.join(parts[:-1]), None, None, [parts[-1]], 0)
try:
return getattr(obj, parts[-1])
except AttributeError:
raise ImportError("No module named %s" % parts[-1])
复制代码
importlib.import_module(name)
复制代码
上面2种方法均可以,github上代码里2种方法都有测试。
检索resources文件夹,生成路由映射,并导入对应实现类, 代码演示以下:
def route(route_file_path, resources_name="resources", route_prefix="", existing_route=None):
route_list = []
def get_route_tuple(file_name, route_pre, resource_module_name):
""" :param file_name: API file name :param route_pre: route prefix :param resource_module_name: resource module """
nonlocal route_list
nonlocal existing_route
route_endpoint = file_name.split(".py")[0]
#module = importlib.import_module('{}.{}'.format(
# resource_module_name, route_endpoint))
module = import_object('{}.{}'.format(
resource_module_name, route_endpoint))
route_class = underline_to_hump(route_endpoint)
real_route_endpoint = r'/{}{}'.format(route_pre, route_endpoint)
if existing_route and isinstance(existing_route, dict):
if real_route_endpoint in existing_route:
real_route_endpoint = existing_route[real_route_endpoint]
route_list.append((real_route_endpoint, getattr(module, route_class)))
def check_file_right(file_name):
if file_name.startswith("_"):
return False
if not file_name.endswith(".py"):
return False
if file_name.startswith("."):
return False
return True
def recursive_find_route(route_path, sub_resource, route_pre=""):
nonlocal route_prefix
nonlocal resources_name
file_list = os.listdir(route_path)
if config.DEBUG:
print("FileList:", file_list)
for file_item in file_list:
if file_item.startswith("_"):
continue
if file_item.startswith("."):
continue
if os.path.isdir(route_path + "/{}".format(file_item)):
recursive_find_route(route_path + "/{}".format(file_item), sub_resource + ".{}".format(file_item), "{}{}/".format(route_pre, file_item))
continue
if not check_file_right(file_item):
continue
get_route_tuple(file_item, route_prefix + route_pre, sub_resource)
recursive_find_route(route_file_path, resources_name)
if config.DEBUG:
print("RouteList:", route_list)
return route_list
复制代码
以flask框架为例,其他框架请看github中的代码演示。 app.py 中代码
app = Flask(__name__)
api = Api(app)
# APi route and processing functions
exist_route = {"/flask/hello_world": "/hello_world"}
route_path = "./resources"
route_list = route(
route_path,
resources_name="resources",
route_prefix="flask/",
existing_route=exist_route)
for item in route_list:
api.add_resource(item[1], item[0])
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(parse_args.port), debug=config.DEBUG)
复制代码
运行app.py以后,路由打印以下:
RouteList: [
('/hello_world', <class'resources.hello_world.HelloWorld'>),\ ('/flask/ab/testab/hello_world_python_test', <class 'resources.ab.testab.hello_world_python_test.HelloWorldPythonTest'>), \
('/flask/ab/hello_world_python', <class 'resources.ab.hello_world_python.HelloWorldPython'>)
]
复制代码
元组第一个元素则是路由,第二个元素是对应的实现类。
总结: 至此,经过制定必定规则,解放路由管理文件方案完成。
更多文章请关注公众号 『天澄技术杂谈』