猿问

在上下文管理器中运行子命令

在 python clickCLI 应用程序的上下文中,我想在上下文管理器中运行一个子命令,该子命令将在更高级别的命令中设置。怎么可能做到这一点click?我的伪代码看起来像:



import click


from contextlib import contextmanager


@contextmanager

def database_context(db_url):

    try:

        print(f'setup db connection: {db_url}')

        yield

    finally:

        print('teardown db connection')



@click.group

@click.option('--db',default='local')

def main(db):

    print(f'running command against {db} database')

    db_url = get_db_url(db)

    connection_manager = database_context(db_url)

    # here come the mysterious part that makes all subcommands

    # run inside the connection manager


@main.command

def do_this_thing()

    print('doing this thing')


@main.command

def do_that_thing()

    print('doing that thing')


这将被称为:


> that_cli do_that_thing

running command against local database

setup db connection: db://user:pass@localdb:db_name

doing that thing

teardown db connection


> that_cli --db staging do_this_thing

running command against staging database

setup db connection: db://user:pass@123.456.123.789:db_name

doing this thing

teardown db connection

编辑:请注意,上面的示例是为了更好地说明 缺少的功能而伪造的click,而不是我想特别解决这个问题。我知道我可以在所有命令中重复相同的代码并达到相同的效果,这在我的实际用例中已经做到了。我的问题正是关于我只能在主函数中做什么,它会在上下文管理器中运行所有透明的子命令。


拉莫斯之舞
浏览 206回答 2
2回答

12345678_0001

装饰命令定义一个上下文管理器装饰用contextlib.ContextDecorator使用click.pass_context装饰器 on main(),以便您可以探索单击上下文创建db_context上下文管理器的实例迭代为组定义的命令main使用ctx.command.commands对于每个命令,使用上下文管理器修饰的相同回调替换原始回调(命令调用的函数) db_context(cmd)通过这种方式,您将以编程方式修改每个命令,使其行为类似于:@main.command()@db_contextdef do_this_thing():    print('doing this thing')但无需更改您的功能之外的代码main()。有关工作示例,请参阅下面的代码:import clickfrom contextlib import ContextDecoratorclass Database_context(ContextDecorator):    """Decorator context manager."""    def __init__(self, db_url):        self.db_url = db_url    def __enter__(self):        print(f'setup db connection: {self.db_url}')    def __exit__(self, type, value, traceback):        print('teardown db connection')@click.group() @click.option('--db', default='local')@click.pass_contextdef main(ctx, db):    print(f'running command against {db} database')    db_url = db  # get_db_url(db)# here come the mysterious part that makes all subcommands# run inside the connection manager    db_context = Database_context(db_url)           # Init context manager decorator    for name, cmd in ctx.command.commands.items():  # Iterate over main.commands        cmd.allow_extra_args = True                 # Seems to be required, not sure why        cmd.callback = db_context(cmd.callback)     # Decorate command callback with context manager@main.command()def do_this_thing():    print('doing this thing')@main.command()def do_that_thing():    print('doing that thing')if __name__ == "__main__":    main()它执行您在问题中描述的内容,希望它能在实际代码中按预期工作。使用 click.pass_context下面的代码将让您了解如何使用click.pass_context.import clickfrom contextlib import contextmanager@contextmanagerdef database_context(db_url):    try:        print(f'setup db connection: {db_url}')        yield    finally:        print('teardown db connection')@click.group()@click.option('--db',default='local')@click.pass_contextdef main(ctx, db):    ctx.ensure_object(dict)    print(f'running command against {db} database')    db_url = db #get_db_url(db)    # Initiate context manager    ctx.obj['context'] = database_context(db_url)@main.command()@click.pass_contextdef do_this_thing(ctx):    with ctx.obj['context']:        print('doing this thing')@main.command()@click.pass_contextdef do_that_thing(ctx):    with ctx.obj['context']:        print('doing that thing')if __name__ == "__main__":    main(obj={})避免显式with语句的另一种解决方案可能是使用 将上下文管理器作为装饰器传递contextlib.ContextDecorator,但使用进行设置可能会更复杂click。

慕的地6264312

此用例在 Click from v8.0 中通过使用 ctx.with_resource(context_manager)https://click.palletsprojects.com/en/8.0.x/api/#click.Context.with_resourceClick 高级文档中有一个工作示例https://click.palletsprojects.com/en/8.0.x/advanced/#managing-resources
随时随地看视频慕课网APP

相关分类

Python
我要回答