开源小项目:pyouter 0.0.1 发布

发布一个业余开源小项目 pyouter

  • python Github 仓库:`https://github.com/fanfeilong/pyouter
  • pip install pyouter
  • contributors: @幻灰龙 @ccat

传统的命令行参数一般会设计 -x, --xxx这样的命令解析。一个痛点是命令多了之后就难以记忆和使用。同时不易于通过命令行定向控制任务执行。

受 HTTP Rest API path和路由器的影响。最开始我在项目中让命令行采用基于点分隔符来完成 path 的功能,例如:

python main.py -a ocr
python main.py -a ask
python main.py -a ask.answer
python main.py -a ask.skill_tree
python main.py -a ask.skill_tree.match

实现上解析 ask.skill_tree.match 后就可以有层次地路由,在内部的路由映射表里递归查找,一直找到叶子节点就执行叶子节点的配置任务。

最开始的路由解析代码非常简单:

def dispatch(config, options, actions, targets):
    ''' 分发命令行 action '''
    action_len = len(actions)
    action_len = len(actions)
    if action_len < 2:
        return

    index = 1
    next = targets
    action = actions[index]
    print(f"[命令路由中..]: {actions[0]}")

    while action_len >= index:
        if type(next) == type({}):
            if index == action_len:
                if next.get('run') != None:
                    print(f"[命令路由执行]:", '->'.join(actions))
                    next['run']()
                    break

            action = actions[index]
            if next.get(action) != None:
                print(f"[命令路由中..]: {action}")
                next = next[action]
                index += 1
            else:
                print("[命令路由错误]: 未找到支持的命令行路由:", '->'.join(actions))
                index += 1
        else:
            print(f"[命令路由执行]:", '->'.join(actions))

            next()
            index += 1
            break

用例:

if __name__=='__main__':
	config = ...
	options = ...
	dispatch(config, options, actions, {
	    "ask": {
	        "run": lambda: dispatch_ask(config, options),
	        "answer": lambda: dispatch_answer(config, options),
	        "code": lambda: dispatch_code(config, options),
	        "tag": lambda: dispatch_tag(config, options),
	        "title": lambda: dispatch_title(config, options),
	        "skill_tree": {
	            "run": lambda: dispatch_skill_tree(config, options),
	            "main": lambda: dispatch_skill_tree_main(config, options),
	            "match": lambda: dispatch_skill_tree_match(config, options),
	        },
	        "book": lambda: dispatch_book(config, options),
	        "level": lambda: dispatch_level(config, options),
	        "pycorrector": lambda: dispatch_pycorrector(config, options),
	        "tag_index": lambda: dispatch_tag_index(config, options)
	    },
	    "ocr": lambda: dispatch_ocr(config, options),
	    "bert": lambda: dispatch_bert(config, options),
	    "blog_tag_clean": lambda: dispatch_blog_tag_clean(config, options)
	})

@ccat 同学觉的这个想法很好,非常勤快地用对象的方式基于递归重新实现了一个版本路由器。

from .errors import NotFound


class Router(object):
    def __init__(self, **args):
        self.route = {}
        for key in args:
            self.route[key] = args[key]

    def context(self, config, options):
        self.config = config
        self.options = options
        for key in self.route:
            router = self.route[key]
            if type(router) == type(self):
                router.context(config, options)

    def dispatch(self, command: str):
        if "." in command:
            crt, nxt = command.split('.', 1)
            if crt not in self.route:
                raise NotFound(self.route, crt)
            if self.options.view:
                print(f'->router: {crt}')
            return self.route[crt].dispatch(nxt)
        else:
            if self.options.view:
                print(f'->action: {command}')
            if command not in self.route.keys():
                raise NotFound(self.route, command)

            return self.route[command](self.config, self.options)

    def tasks(self, base=None):

        for key in self.route:
            current = f"{base}.{key}" if base else key
            item = self.route[key]
            if type(item) is Router:
                for task in item.tasks(current):
                    yield task
            else:
                yield current

我们创建了一个仓库:https://github.com/fanfeilong/task_router 以后可以在CodeChina上也同步一个。这个版本的实现很简洁,于是我也贡献了App组织层:

from pyouter.default import create_parser
from pyouter.errors import NotInit
from pyouter.router import Router


class App(object):
    def __init__(self, **args):
        opt_parser = create_parser("tasks router runner")
        self.options = opt_parser.parse_args()
        self.config = {}
        self.router: Router

    def use(self, router: Router):
        self.router = router
        self.router.context(self.config, self.options)
        return self

    def run(self):
        if self.router is None:
            raise NotInit("self.router in App")

        if self.options.tasks:
            for task in self.router.tasks():
                print(task)
        else:
            self.router.dispatch(self.options.actions)
        return self

这样最终使用起来长这样:

from pyouter.router import Router
from pyouter.oh_my_zsh.install import install as omz
from pyouter.fish.install import install as fish
from app import App


if __name__ == "__main__":
    app = App()
    app.use(
        router=Router(
            install=Router(
                ohmyzsh=omz,
                fish=fish)))
    app.run()

用例:

python main.py router.install.fish
python main.py router.install.ohmyzsh

pip 类库安装:pip install pyouter

第1版本比较简单,但是核心设计思想已经定了,更多丰富的功能会持续更新。欢迎试用!不要让 Python 代码变成一堆碎片脚本,请用pyouter组织你的Python脚本任务,提供一个有层次的易于记忆的命令行参数。

更新 0.0.9 支持 async 函数和对象,示例:

from typing import Any
from pyouter.app import App
from pyouter.router import Router

import json
import asyncio
import time


async def hello(config, options):
    print("hello function")
    
def hello_sync(config, options):
    print("hello sync")

class Hello:
    def __init__(self, value) -> None:
        self.value = value
    
    # 如果是一个class,需要有 run(self, config, options) 成员函数
    async def run(self, config, options) -> Any:
        self.config = config
        self.options = options
        await self.inner()
    
    async def inner(self):
        print(f"run Hello, {self.value}")
        await asyncio.sleep(2)
        print(f"hello class object, self.options.debug:{self.options.debug}, sleep 2s")
        if self.options.debug:
            print(f'debug, {self.value}')
            
class Hello_Sync:
    def __init__(self, value) -> None:
        self.value = value
    
    # 如果是一个class,需要有 run(self, config, options) 成员函数
    def run(self, config, options) -> Any:
        self.config = config
        self.options = options
        self.inner()
    
    def inner(self):
        print(f"run Hello_Sync, {self.value}")
        time.sleep(self.value)
        print(f"hello class object, self.options.debug:{self.options.debug}, sleep {self.value}s")
        if self.options.debug:
            print(f'debug, {self.value}')

if __name__=="__main__":
    '''
    Usage:
        ## execute:
        * python test.py test.hello.func
        * python test.py test.hello.obj
        * python test.py test
        
        ## dump router path only:
        * python test.py test.hello -i
        * python test.py test.hello --insepect
        
        ## dump router path and execute:
        * python test.py test.hello -v
        * python test.py test.hello --view
    '''
    
    app = App(
        config='config.json',
    )
    
    app.option(
        '-d', '--debug',
        dest='debug',
        action="store_true",
        help='debug'
    )
    
    app.use(
        router=Router(
            test=Router(
                hello=Router(
                    func=hello,
                    obj=Hello("world"),
                    obj2=Hello_Sync(10)
                ),
                hello2=hello,
                hello3=Router(
                    func=hello_sync,
                    obj=Hello("world"),
                    obj2=Hello_Sync(20),
                )
            )
        )
    )
    
    app.run()
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值