You need to enable JavaScript to run this app. 基于RPC构建控制守护进程的CLI客户端的实现问题咨询
优惠活动
大模型
产品
解决方案
定价
更多

基于RPC构建控制守护进程的CLI客户端的实现问题咨询

问题根因

你遇到的两个问题本质是使用方式不符合Click和XMLRPC的设计逻辑:

  1. AttributeError: 'Group' object has no attribute '__name__':被@click.group装饰后返回的是Click的Group类实例,并非原生函数,而XMLRPC注册函数要求传入的可调用对象必须带有__name__属性,因此直接注册会报错。
  2. 输出打印在守护进程侧:Click默认会将命令的打印内容、返回值直接输出到运行进程的stdout/stderr,你在服务端直接调用Click组,输出自然只会留在服务端,不会自动序列化返回给客户端。

修复方案

方案1:业务逻辑与CLI解耦,CLI放在客户端实现(推荐)

不要直接注册Click的Group对象到RPC服务,将纯业务逻辑抽离单独注册,客户端用Click做参数解析,调用对应RPC方法拿到结果后本地打印。

服务端代码

######## Daemon ########
from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.server import SimpleXMLRPCRequestHandler

# 抽离纯业务逻辑,和Click完全解耦
def add(a, b):
    return int(a) + int(b)

def subtract(a, b):
    return int(a) - int(b)

class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = ('/RPC2',)

if __name__ == "__main__":
    with SimpleXMLRPCServer(('localhost', 8000), requestHandler=RequestHandler) as server:
        server.register_introspection_functions()
        # 直接注册业务函数
        server.register_function(add)
        server.register_function(subtract)
        print("守护进程已启动,监听8000端口")
        server.serve_forever()

客户端代码

####### Client ########
import click
import xmlrpc.client

@click.group()
def cli():
    pass

@cli.command()
@click.argument("a")
@click.argument("b")
def add(a, b):
    with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
        res = proxy.add(a, b)
        # 结果在客户端本地打印
        click.echo(f"计算结果:{res}")

@cli.command()
@click.argument("a")
@click.argument("b")
def subtract(a, b):
    with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
        res = proxy.subtract(a, b)
        click.echo(f"计算结果:{res}")

if __name__ == "__main__":
    cli()

使用时直接在客户端侧执行python client.py add 1 2即可拿到输出,所有打印内容都留在客户端。

方案2:CLI定义放在服务端,封装调用捕获输出

如果希望统一维护CLI定义、避免客户端重复写参数规则,可以在服务端封装调用逻辑,捕获Click的输出后返回给客户端。

服务端封装示例

import io
import click
from contextlib import redirect_stdout, redirect_stderr
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler

@click.group()
def cli():
    pass

@cli.command()
@click.argument("a")
@click.argument("b")
def add(a,b):
    click.echo(int(a) + int(b))

@cli.command()
@click.argument("a")
@click.argument("b")
def subtract(a,b):
    click.echo(int(a) - int(b))

# 封装RPC调用入口,接收参数列表并返回执行结果
def run_cli(args):
    out_buffer = io.StringIO()
    err_buffer = io.StringIO()
    try:
        with redirect_stdout(out_buffer), redirect_stderr(err_buffer):
            cli.main(args=args)
        return {"code": 0, "stdout": out_buffer.getvalue(), "stderr": err_buffer.getvalue()}
    except SystemExit as e:
        return {"code": e.code, "stdout": out_buffer.getvalue(), "stderr": err_buffer.getvalue()}

class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = ('/RPC2',)

if __name__ == "__main__":
    with SimpleXMLRPCServer(('localhost', 8000), requestHandler=RequestHandler) as server:
        server.register_introspection_functions()
        # 注册封装后的调用函数
        server.register_function(run_cli)
        print("守护进程已启动,监听8000端口")
        server.serve_forever()

客户端调用示例

import sys
import xmlrpc.client

if __name__ == "__main__":
    # 将命令行参数直接传递给服务端
    args = sys.argv[1:]
    with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
        res = proxy.run_cli(args)
        print(res["stdout"], end="")
        print(res["stderr"], end="")
        exit(res["code"])

这种方案不需要在客户端维护CLI规则,服务端更新命令后客户端无需修改即可直接使用。

内容的提问来源于stack exchange,提问作者sev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.16 14:15:05