隨着業務的飛速發展,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
hello_world_python.py路由入口文件會自動映射,映射規則爲: 前綴 / 項目文件夾[...] / 文件名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'>)
]
複製代碼
元組第一個元素則是路由,第二個元素是對應的實現類。
總結: 至此,經過制定必定規則,解放路由管理文件方案完成。
更多文章請關注公衆號 『天澄技術雜談』