Python 插件式程序設計與開發實踐總結

插件式程序設計與開發實踐總結python

By:授客 QQ:1033553122 json

開發環境api

win 10 app

python 3.6.5函數

 

代碼結構spa

 

 

 

 

需求描述插件

如上,以user.py爲程序入口腳本,運行該腳本時,須要建立一個user類對象,執行一系列動做(包含一系列動做的列表)。程序執行動做前,要求先獲取動做名稱,根據該名稱,執行不一樣的操做。這些操做,對應不一樣的類函數。設計

 

 

實現思路component

大體實現思路就是,把user對象須要運行的類函數(使用@classmethod修飾的函數,可不用建立對象進行調用),看成插件函數,並設置爲user的屬性,這樣程序運行時,可經過該屬性來調用對應的類函數。這裏的問題是,程序怎麼知道執行哪一個類函數呢?到目前爲止,程序只能根據動做名稱來判斷待執行的操做,因此,須要創建動做名稱和類函數的映射關係。對象

 

怎麼創建動做名稱和類函數的映射關係呢?這裏用到了裝飾器,新建一個裝飾器類ActionDecorator,爲該類設置一個字典類型的類屬性ACTION_FUNC_CLASS_MODULE_MAP,用這個類來存放動做名稱和類函數的映射關係。咱們把須要看成插件函數的類函數都用該裝飾器進行修飾。

 

這裏,筆者發現一個特性,就是對應模塊被導入時,對應模塊,對應類函數若是使用了裝飾器,該裝飾器函數會被執行。同時,筆者還發現另一個特性,

首次對某個包執行import操做時,該包下面的__init__.py文件將優先被執行。基於這兩個特性,咱們把裝飾器放在用於管理插件類函數的外圍軟件包下(例中的components包),同時,在該外圍軟件包下的__init__.py中加入動態加載插件模塊的代碼:遍歷外圍軟件包下的全部非__init__.py文件,而且動態加載改模塊。這樣,當在user.py入口文件中,執行from components.decoraters.action_decorater import ActionDecorator時,會自動執行components/__init__.py文件,動態加載全部插件模塊,而且自動觸發裝飾器的執行,裝飾器方法執行,會自動根據提供的方法參數創建動做名稱和類函數的映射關係。

 

而後,在初始化user對象時,給該對象動態設置屬性,屬性名稱設置爲動做名稱,屬性值設置爲類方法,這樣,執行動做時,就能夠根據動做名稱調用對應的類方法了。

 

 

代碼實現

 

 

action_decorate.py

 

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@CreateTime: 2020/12/09 14:58
@Author : shouke
'''

class ActionDecorator(object):
    '''
    action 裝飾器
    '''

    ACTION_FUNC_CLASS_MODULE_MAP = {}

    @classmethod
    def action_register(cls, action, class_name, function_name, module_path):
        def wrapper(func):
            cls.ACTION_FUNC_CLASS_MODULE_MAP.update({action: {'class_name':class_name, 'function_name':function_name, 'module_path':module_path}})
            return func
        return wrapper

 

  

 

 

 

components/__init__.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@Author : shouke
'''


import os.path
import importlib

def load_plugin_modules():
    '''遞歸加載當前目錄下的全部模塊'''


    head, tail = os.path.split(__file__)
    package_father_path, package = os.path.split(head)

    def load_modules(dir_path):
        nonlocal package_father_path
        if not os.path.isdir(dir_path):
            return
        for name in os.listdir(dir_path):
            full_path = os.path.join(dir_path, name)
            if os.path.isdir(full_path):
                load_modules(full_path)
            elif not name.startswith('_') and name.endswith('.py'):
                temp_path = full_path.replace(package_father_path, '')
                relative_path = temp_path.replace('\\', '/').lstrip('/').replace('/', '.')
                importlib.import_module(relative_path.rstrip('.py'), package=package)
    load_modules(head)

# 加載模塊,自動觸發裝飾器,獲取相關插件函數相關信息

load_plugin_modules()

  

 

 

assertioner.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@Author : shouke
'''

from components.decoraters.action_decorater import ActionDecorator

class Assertioner(object):
    @classmethod
    @ActionDecorator.action_register('assert_equal', 'Assertioner', 'assert_equal', __name__)
    def assert_equal(self,  config:dict, *args, **kwargs):
        print('執行斷言')
        print('斷言配置:\n', config)

  

 

 

send_request.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@Author : shouke
'''



from components.decoraters.action_decorater import ActionDecorator

class Requester(object):
    @ActionDecorator.action_register('send_request', 'Requester', 'send_request', __name__)
    @classmethod
    def send_request(self, config:dict, *args, **kwargs):
        print('發送請求')
        print('請求配置:')
        print(config)

  

 

 

example.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@CreateTime: 2020/12/10 15:51
@Author : shouke
'''


from components.decoraters.action_decorater import ActionDecorator

class CustomClassName(object):
    @ActionDecorator.action_register('custom_action_name', 'CustomClassName', 'action_func_name', __name__)
    @classmethod
    def action_func_name(self, config:dict, *args, **kwargs):
        '''
        example
        user_instance: kwargs['user'] # 壓測用戶實例
        '''

        # do something you want

# 說明 plugings目錄下可自由建立python包,管理插件,固然,也能夠位於components包下其它任意位置建立python包,管理插件(不推薦)

  

 

user.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@Author : shouke
'''

from components.decoraters.action_decorater import ActionDecorator

class User(object):

    def __init__(self):
        for action, action_map in ActionDecorator.ACTION_FUNC_CLASS_MODULE_MAP.items():
            module = __import__(action_map.get('module_path'), fromlist=['True'])
            class_cls = getattr(module, action_map.get('class_name'))
            setattr(self, action, getattr(class_cls, action_map.get('function_name')))

    def run_actions(self, actions):
        ''' 執行一系列動做 '''

        for step in actions:
            action = step.get('action')

            if hasattr(self, action):
                getattr(self, action)(step, user=self)


if __name__ == '__main__':
    actions = [{
         "action": "send_request",
         "name": "請求登陸", #可選配置,默認爲None
         "method": "POST",
         "path": "/api/v1/login",
         "body": {
            "account": "shouke",
            "password": "123456"
         },
         "headers": {
             "Content-Type": "application/json"
         }
    },
    {
         "action": "assert_equal",
         "name": "請求響應斷言",
         "target": "body",
         "rule": "assert_contain",
         "patterns": ["shouke","token"],
         "logic":"or"
    }]
    User().run_actions(actions)

  

 

 

運行結果

 

發送請求

請求配置:

{'action': 'send_request', 'name': '請求登陸', 'method': 'POST', 'path': '/api/v1/login', 'body': {'account': 'shouke', 'password': '123456'}, 'headers': {'Content-Type': 'application/json'}}

執行斷言

斷言配置:

{'action': 'assert_equal', 'name': '請求響應斷言', 'target': 'body', 'rule': 'assert_contain', 'patterns': ['shouke', 'token'], 'logic': 'or'}

相關文章
相關標籤/搜索