Команды декорирования
- Определение менеджера контекста Декоратор с использованием
contextlib.ContextDecorator
- Использование
click.pass_context
декоратор в main()
, так что вы можете исследовать контекст клика - Создать экземпляр
db_context
диспетчера контекста - Выполнять итерации по командам, определенным для группы
main
, используя ctx.command.commands
- Для каждой команды замените исходный обратный вызов (функция, вызываемую командой) тем же обратным вызовом, украшенным менеджером контекста
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
.
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
.