可插拔的Python子命令模式?

时间:2019-01-19 00:49:34

标签: python python-3.x

我正在寻找一种实现Python子命令的好模式,其中主命令在运行时查找子命令(而不是知道所有可能的子命令的列表;这允许“应用程序”可以轻松地通过新的子命令进行扩展,而无需更改主代码)

例如:

 topcmd.py foo

将在/some/dir中查找foo.py,如果存在,请运行它。或它的一些变化。

foo.py中调用的代码最好是在类或对象上定义明确的函数或方法。

3 个答案:

答案 0 :(得分:2)

您可以使用// React router transitions .fade-appear, .fade-enter { opacity: 0; z-index: 1; } .fade-appear-active, .fade-enter.fade-enter-active { opacity: 1; transition: opacity 300ms linear; } .fade-exit { opacity: 1; } .fade-exit.fade-exit-active { opacity: 0; transition: opacity 300ms linear; } 函数使用在命令行中传递的字符串名称来动态导入模块。

__import__

错误处理等留给读者练习

编辑:这将取决于通过pip安装的用户插件。如果您希望用户不安装而将插件拖放到文件夹中,则必须将该文件夹添加到python路径中。

答案 1 :(得分:1)

尽管这个问题实际上很广泛,但是在典型的默认Python安装(例如,setuptools)中有足够的工具是可以相对实现的,并且实际上是可以扩展的,因此可以使用其他软件包创建/安装的方式可以为您的主程序提供新的,可发现的子命令。

您的基本软件包可以提供console_scripts形式的标准entry_point,它指向您的入口点,该入口点会将所有参数馈入某个参数解析器(例如argparse)的实例中,您可以在与console_scripts类似的方案下实施的一种注册表,除了在您的特定entry_points组下,以便它可以遍历每个条目并实例化还将提供自己的ArgumentParser实例的对象,主入口点将作为子命令动态地注册到其自身,从而向您的用户显示哪些子命令实际上可用以及其调用可能是什么样的。

举个例子,在主软件包的setup.py中,您可能会有类似

的条目
setup(
    name='my.package',
    # ...
    entry_points={
        'console_scripts': [
            'topcmd = my.package.runtime:main',
        ],
        'my.package.subcmd': [
            'subcmd1 = my.package.commands:subprog1',
            'subcmd2 = my.package.commands:subprog2',
        ],
    },
    # ...
)

my/package/runtime.py源文件中,main方法将必须构造一个新的ArgumentParser实例,并在通过pkg_resources.working_set提供的入口点进行迭代时,例如:

from pkg_resources import working_set

def init_parser(argparser):  # pass in the argparser provided by main
    commands = argparser.add_subparsers(dest='command')
    for entry_point in working_set.iter_entry_points('my.package.subcmd'):
        subparser = commands.add_parser(entry_point.name)
        # load can raise exception due to missing imports or error in object creation
        subcommand = entry_point.load()
        subcommand.init_parser(subparser) 

因此,在main函数中,可以将它创建的argparser实例传递到上面的函数中,然后将加载入口点'subcmd1 = my.package.commands:subprog1'。在my/package/command.py内部,必须提供一个实现的init_parser方法,该方法将使用提供的子解析器,并使用所需的参数填充该子解析器:

class SubProgram1(object):
    def init_parser(self, argparser)
        argparser.add_argument(...) 

subprog1 = SubProgram1() 

哦,最后一件事,在将参数传递到主argparser.parse_args(...)之后,将命令的名称提供给argparser.command。应该可以将其更改为实际实例,但是可能无法实现您真正想要的(因为主程序可能想在实际使用该命令之前做进一步的工作/验证)。该部分是另一个复杂的部分,但是至少参数解析器应该包含实际运行正确的子程序所需的信息。

自然,这绝对不包括错误检查,并且必须以某种形式实现,以防止错误的子命令类炸毁主程序。我使用了这种模式(尽管实现起来更为复杂),该模式可以支持任意数量的嵌套子命令。想要实现自定义命令的程序包也可以简单地将自己的条目添加到自己的my.package.subcmd的入口点组(在这种情况下,添加到setup.py)中。例如:

setup(
    name="some.other.package",
    # ...
    entry_points={
        'my.package.subcmd': [
            'extracmd = some.other.package.commands:extracmd',
        ],
    },
    # ...
)

附录:

根据要求,生产中使用的实际实现位于我当前维护的软件包(calmjs)中。将那个软件包安装(到virtualenv中)并在命令行上运行calmjs应该会显示与主软件包entry points中定义的条目相同的子命令列表。现在,安装扩展功能的附加软件包(例如calmjs.webpack)并再次运行calmjs,将把calmjs.webpack列为附加子命令。

入口点将子类的实例引用到Runtime类,并且在其中the subparser is added且如果满足注册要求(以下与各种错误/健全性检查相关的许多语句,例如,当多个程序包为运行时实例定义相同的子命令名称时的操作,等等),registered到该特定运行时实例上的argparser实例,以及封装了该运行时实例的subparser is passed into the init_argparser method子命令。例如,calmjs webpack子命令子解析器是通过its init_argparser方法设置的,并且该软件包registers the webpack subcommand位于其自己的setup.py中。 (要与他们一起玩,只需使用pip安装相关的软件包即可。)

答案 2 :(得分:0)

最简单的答案似乎是,如果我所有的命令都在foo.commands中:

import foo.commands
import importlib

for importer, modname, ispkg in pkgutil.iter_modules(foo.commands.__path__):
    mod=importlib.import_module('foo.commands.' + cmd)
    mod.run()

这将运行所有子命令。 (好吧,在实际代码中,我将只运行一个。这是方法。)