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

标签 python python-3.x command-line-interface python-click

在 python click CLI 应用程序的上下文中,我想在上下文管理器中运行一个子命令,该子命令将在更高级别的命令中设置。如何通过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 缺失的功能而伪造的,并不是我想特别解决这个问题。我知道我可以在所有命令中重复相同的代码并达到相同的效果,我已经在实际用例中做到了这一点。我的问题恰恰是我在主函数中可以做什么,这将在上下文管理器中透明地运行所有子命令。

最佳答案

装饰命令

  1. 使用 contextlib.ContextDecorator 定义上下文管理器装饰器
  2. 使用click.pass_context main() 上的装饰器,以便您可以探索点击上下文
  3. 创建上下文管理器的实例 db_context
  4. 使用 ctx.command.commands 迭代为组 main 定义的命令
  5. 对于每个命令,将原始回调(命令调用的函数)替换为用上下文管理器修饰的相同回调db_context(cmd)

这样您就可以通过编程方式修改每个命令,使其行为如下:

@main.command()
@db_context
def do_this_thing():
    print('doing this thing')

但不需要更改函数 main() 之外的代码。

请参阅下面的代码以获取工作示例:

import click
from contextlib import ContextDecorator


class 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_context
def 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 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')
@click.pass_context
def 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_context
def do_this_thing(ctx):
    with ctx.obj['context']:
        print('doing this thing')

@main.command()
@click.pass_context
def do_that_thing(ctx):
    with ctx.obj['context']:
        print('doing that thing')

if __name__ == "__main__":
    main(obj={})

避免显式 with 语句的另一个解决方案是使用 contextlib.ContextDecorator 将上下文管理器作为装饰器传递。 ,但使用 click 设置可能会更复杂。

关于python - 在上下文管理器中运行子命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54950613/

相关文章:

linux - 如何使 bash 程序接受来自另一个 C 程序的命令

php - Xdebug laravel artisan 命令

python - 如何统一扩展列表以包括外推平均值?

python - 为什么不能在 f 弦的大括号内使用反斜杠?我该如何解决这个问题?

python-3.x - redis - 如何创建事务

python - 如何生成未填充空间的骨架点?

Bash - 在文件中找到一个关键字并删除它的行

python - 在 M2Crypto 中访问 CSR 扩展堆栈

python - 识别并计算数据框中前 x 个对象的统计信息

python - 给定两个顶点围绕中心点旋转线