장식 명령 용기
하나 내가 최근에 발견 및 클래스에 적용 할 수있는 장식을 정의하는 시작하는 것입니다 작동하는 것 같다 한이 문제에 대한 해결 방안. 프로그래머는 명령을 클래스의 전용 멤버로 정의하고 데코레이터는 명령의 콜백을 기반으로하는 클래스의 공용 함수 멤버를 만듭니다. 예를 들어, _bar
명령을 포함하는 Foo
클래스는 새로운 함수 bar
을 얻습니다 (Foo.bar
이 아직 존재하지 않는다고 가정).
이 작업을 수행하면 원래 명령이 그대로 유지되므로 기존 코드가 손상되지 않습니다. 이러한 명령은 개인용이므로 생성 된 문서에서 생략해야합니다. 그러나이 기능을 기반으로하는 기능은 공개로 인해 문서에 표시되어야합니다. 그게 내 대부분의 명령은 내가 현재 일하고 있어요 프로젝트에 정의하는 방법이기 때문에
def ensure_cli_documentation(cls):
"""
Modify a class that may contain instances of :py:class:`click.BaseCommand`
to ensure that it can be properly documented (e.g. using tools such as Sphinx).
This function will only process commands that have private callbacks i.e. are
prefixed with underscores. It will associate a new function with the class based on
this callback but without the leading underscores. This should mean that generated
documentation ignores the command instances but includes documentation for the functions
based on them.
This function should be invoked on a class when it is imported in order to do its job. This
can be done by applying it as a decorator on the class.
:param cls: the class to operate on
:return: `cls`, after performing relevant modifications
"""
for attr_name, attr_value in dict(cls.__dict__).items():
if isinstance(attr_value, click.BaseCommand) and attr_name.startswith('_'):
cmd = attr_value
try:
# noinspection PyUnresolvedReferences
new_function = copy.deepcopy(cmd.callback)
except AttributeError:
continue
else:
new_function_name = attr_name.lstrip('_')
assert not hasattr(cls, new_function_name)
setattr(cls, new_function_name, new_function)
return cls
클래스
의 명령으로이 솔루션은 명령이 내부 클래스입니다 가정하는 이유를 문제를 피하는 것은 - 나는 yapsy.IPlugin.IPlugin
의 하위 클래스에 포함 된 플러그인으로 내 명령의 대부분을로드합니다. 명령의 콜백을 클래스 인스턴스 메서드로 정의하려는 경우 CLI를 실행하려고 할 때 click이 명령 콜백에 self
매개 변수를 제공하지 않는 문제가 발생할 수 있습니다.이것은 당신의 콜백을 무두질하여 해결 아래와 같이 할 수있다 :
class Foo:
def _curry_instance_command_callbacks(self, cmd: click.BaseCommand):
if isinstance(cmd, click.Group):
commands = [self._curry_instance_command_callbacks(c) for c in cmd.commands.values()]
cmd.commands = {}
for subcommand in commands:
cmd.add_command(subcommand)
try:
if cmd.callback:
cmd.callback = partial(cmd.callback, self)
if cmd.result_callback:
cmd.result_callback = partial(cmd.result_callback, self)
except AttributeError:
pass
return cmd
예
이 모두 함께이 퍼팅 :
Example: Adding two numbers
1 + 2 = 3
Example: Printing usage
Usage: cli calc add [OPTIONS] X Y
adds two numbers
Options:
--help Show this message and exit.
Process finished with exit code 0
:
from functools import partial
import click
from click.testing import CliRunner
from doc_inherit import class_doc_inherit
def ensure_cli_documentation(cls):
"""
Modify a class that may contain instances of :py:class:`click.BaseCommand`
to ensure that it can be properly documented (e.g. using tools such as Sphinx).
This function will only process commands that have private callbacks i.e. are
prefixed with underscores. It will associate a new function with the class based on
this callback but without the leading underscores. This should mean that generated
documentation ignores the command instances but includes documentation for the functions
based on them.
This function should be invoked on a class when it is imported in order to do its job. This
can be done by applying it as a decorator on the class.
:param cls: the class to operate on
:return: `cls`, after performing relevant modifications
"""
for attr_name, attr_value in dict(cls.__dict__).items():
if isinstance(attr_value, click.BaseCommand) and attr_name.startswith('_'):
cmd = attr_value
try:
# noinspection PyUnresolvedReferences
new_function = cmd.callback
except AttributeError:
continue
else:
new_function_name = attr_name.lstrip('_')
assert not hasattr(cls, new_function_name)
setattr(cls, new_function_name, new_function)
return cls
@ensure_cli_documentation
@class_doc_inherit
class FooCommands(click.MultiCommand):
"""
Provides Foo commands.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._commands = [self._curry_instance_command_callbacks(self._calc)]
def list_commands(self, ctx):
return [c.name for c in self._commands]
def get_command(self, ctx, cmd_name):
try:
return next(c for c in self._commands if c.name == cmd_name)
except StopIteration:
raise click.UsageError('Undefined command: {}'.format(cmd_name))
@click.group('calc', help='mathematical calculation commands')
def _calc(self):
"""
Perform mathematical calculations.
"""
pass
@_calc.command('add', help='adds two numbers')
@click.argument('x', type=click.INT)
@click.argument('y', type=click.INT)
def _add(self, x, y):
"""
Print the sum of x and y.
:param x: the first operand
:param y: the second operand
"""
print('{} + {} = {}'.format(x, y, x + y))
@_calc.command('subtract', help='subtracts two numbers')
@click.argument('x', type=click.INT)
@click.argument('y', type=click.INT)
def _subtract(self, x, y):
"""
Print the difference of x and y.
:param x: the first operand
:param y: the second operand
"""
print('{} - {} = {}'.format(x, y, x - y))
def _curry_instance_command_callbacks(self, cmd: click.BaseCommand):
if isinstance(cmd, click.Group):
commands = [self._curry_instance_command_callbacks(c) for c in cmd.commands.values()]
cmd.commands = {}
for subcommand in commands:
cmd.add_command(subcommand)
if cmd.callback:
cmd.callback = partial(cmd.callback, self)
return cmd
@click.command(cls=FooCommands)
def cli():
pass
def main():
print('Example: Adding two numbers')
runner = CliRunner()
result = runner.invoke(cli, 'calc add 1 2'.split())
print(result.output)
print('Example: Printing usage')
result = runner.invoke(cli, 'calc add --help'.split())
print(result.output)
if __name__ == '__main__':
main()
이 main()
실행,이 출력을 얻을
이 실행 h 스핑크스, 내 브라우저에서이 문서를 볼 수 있습니다 :
![Sphinx documentation](https://i.stack.imgur.com/aa10A.png)