Skip to content

Commit 9d3d377

Browse files
committed
cli: add RPCServer commands
1 parent b904bb7 commit 9d3d377

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

invenio_app/cli.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,132 @@
22
#
33
# This file is part of Invenio.
44
# Copyright (C) 2017-2018 CERN.
5+
# Copyright (C) 2025 Graz University of Technology.
56
#
67
# Invenio is free software; you can redistribute it and/or modify it
78
# under the terms of the MIT License; see LICENSE file for more details.
89

910
"""CLI application for Invenio flavours."""
1011

12+
import contextlib
13+
import io
14+
import pickle
15+
import socket
16+
import socketserver
17+
18+
from click import group, option, pass_context, secho
19+
from flask.cli import with_appcontext
1120
from invenio_base.app import create_cli
1221

1322
from .factory import create_app
1423

1524
#: Invenio CLI application.
1625
cli = create_cli(create_app=create_app)
26+
27+
28+
class RPCRequestHandler(socketserver.BaseRequestHandler):
29+
"""RPCRequestHandler."""
30+
31+
def handle(self):
32+
"""Handles the requests to the RPCServer."""
33+
data = self.request.recv(4096)
34+
if not data:
35+
return
36+
37+
try:
38+
command_parts = pickle.loads(data)
39+
40+
if not isinstance(command_parts, list) or len(command_parts) == 0:
41+
raise ValueError("Invalid command format should be a list.")
42+
43+
if command_parts[0] == "ping":
44+
result = "pong"
45+
else:
46+
output_buffer = io.StringIO()
47+
with contextlib.redirect_stdout(output_buffer):
48+
cli.main(args=command_parts, standalone_mode=False)
49+
result = output_buffer.getvalue().strip()
50+
51+
response = pickle.dumps({"success": True, "result": result})
52+
except Exception as e:
53+
response = pickle.dumps({"success": False, "error": str(e)})
54+
55+
self.request.sendall(response)
56+
57+
58+
class RPCServer(socketserver.TCPServer):
59+
"""RPCServer implementation."""
60+
61+
allow_reuse_address = True
62+
63+
def shutdown_server(self):
64+
"""Shutdown the server."""
65+
secho("Shutting down RPC Server...", fg="green")
66+
self.shutdown()
67+
self.server_close()
68+
69+
70+
def send(host, port, args):
71+
"""Send."""
72+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
73+
s.connect((host, port))
74+
s.sendall(pickle.dumps(list(args)))
75+
76+
return pickle.loads(s.recv(4096))
77+
78+
79+
@group()
80+
def rpc_server():
81+
"""RPC server."""
82+
83+
84+
@rpc_server.command("start")
85+
@option("--port", default=5000)
86+
@option("--host", default="localhost")
87+
@with_appcontext
88+
def rpc_server_start(port, host):
89+
"""Start rpc server."""
90+
server = RPCServer((host, port), RPCRequestHandler)
91+
92+
secho(
93+
f"RPC Server is running on port {host}:{port}... (Press Ctrl+C to stop)",
94+
fg="green",
95+
)
96+
97+
try:
98+
server.serve_forever()
99+
except KeyboardInterrupt:
100+
server.shutdown()
101+
102+
103+
@rpc_server.command(
104+
"send",
105+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
106+
)
107+
@option("--port", default=5000)
108+
@option("--host", default="localhost")
109+
@option("--plain", is_flag=True, default=False)
110+
@pass_context
111+
def rpc_server_send(ctx, port, host, plain):
112+
"""Send."""
113+
response = send(host, port, ctx.args)
114+
115+
if response["success"]:
116+
prefix = "" if plain else "Response: "
117+
color = "green"
118+
message = response["result"]
119+
else:
120+
prefix = "" if plain else "Error: "
121+
color = "red"
122+
message = response["error"]
123+
124+
secho(f"{prefix}{message}", fg=color)
125+
126+
127+
@rpc_server.command("ping")
128+
@option("--port", default=5000)
129+
@option("--host", default="localhost")
130+
def rpc_server_ping(port, host):
131+
"""Ping."""
132+
response = send(host, port, ["ping"])
133+
secho(response["result"])

setup.cfg

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# This file is part of Invenio.
44
# Copyright (C) 2017-2022 CERN.
55
# Copyright (C) 2021 TU Wien.
6-
# Copyright (C) 2022-2024 Graz University of Technology.
6+
# Copyright (C) 2022-2025 Graz University of Technology.
77
#
88
# Invenio is free software; you can redistribute it and/or modify it
99
# under the terms of the MIT License; see LICENSE file for more details.
@@ -48,6 +48,9 @@ tests =
4848
[options.entry_points]
4949
console_scripts =
5050
invenio = invenio_app.cli:cli
51+
rpc-server = invenio_app.cli:rpc_server
52+
flask.commands =
53+
rpc-server = invenio_app.cli:rpc_server
5154
invenio_base.api_apps =
5255
invenio_app = invenio_app:InvenioApp
5356
invenio_base.apps =

0 commit comments

Comments
 (0)