forked from M-Labs/artiq
pc_rpc: support for method listing, frontend: artiq_rpctool, +fixes by SB
This commit is contained in:
parent
6d11da3887
commit
b396f5dd43
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
from artiq.protocols.pc_rpc import Client
|
||||
|
||||
|
||||
def get_argparser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="ARTIQ controller identification tool")
|
||||
parser.add_argument("server",
|
||||
help="hostname or IP of the controller to connect to")
|
||||
parser.add_argument("port", type=int,
|
||||
help="TCP port to use to connect to the controller")
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
args = get_argparser().parse_args()
|
||||
remote = Client(args.server, args.port, None)
|
||||
try:
|
||||
target_names, id_parameters = remote.get_rpc_id()
|
||||
finally:
|
||||
remote.close_rpc()
|
||||
print("Target(s): " + ", ".join(target_names))
|
||||
if id_parameters is not None:
|
||||
print("Parameters: " + id_parameters)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import textwrap
|
||||
import sys
|
||||
|
||||
from artiq.protocols.pc_rpc import Client
|
||||
|
||||
|
||||
def get_argparser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="ARTIQ RPC tool")
|
||||
parser.add_argument("server",
|
||||
help="hostname or IP of the controller to connect to")
|
||||
parser.add_argument("port", type=int,
|
||||
help="TCP port to use to connect to the controller")
|
||||
subparsers = parser.add_subparsers(dest="action")
|
||||
subparsers.required = True
|
||||
subparsers.add_parser("list-targets", help="list existing targets")
|
||||
parser_list_methods = subparsers.add_parser("list-methods",
|
||||
help="list target's methods")
|
||||
parser_list_methods.add_argument("-t", "--target", help="target name")
|
||||
parser_call = subparsers.add_parser("call", help="call a target's method")
|
||||
parser_call.add_argument("-t", "--target", help="target name")
|
||||
parser_call.add_argument("method", help="method name")
|
||||
parser_call.add_argument("args", nargs=argparse.REMAINDER,
|
||||
help="arguments")
|
||||
return parser
|
||||
|
||||
|
||||
def list_targets(target_names, id_parameters):
|
||||
print("Target(s): " + ", ".join(target_names))
|
||||
if id_parameters is not None:
|
||||
print("Parameters: " + id_parameters)
|
||||
|
||||
|
||||
def list_methods(remote):
|
||||
methods = remote.get_rpc_method_list()
|
||||
for name, (argspec, docstring) in sorted(methods.items()):
|
||||
args = ""
|
||||
for arg in argspec["args"]:
|
||||
args += arg
|
||||
if argspec["defaults"] is not None:
|
||||
kword_index = len(argspec["defaults"]) - len(argspec["args"])\
|
||||
+ argspec["args"].index(arg)
|
||||
if kword_index >= 0:
|
||||
if argspec["defaults"][kword_index] == Ellipsis:
|
||||
args += "=..."
|
||||
else:
|
||||
args += "={}".format(argspec["defaults"][kword_index])
|
||||
if argspec["args"].index(arg) < len(argspec["args"]) - 1:
|
||||
args += ", "
|
||||
if argspec["varargs"] is not None:
|
||||
args += ", *{}".format(argspec["varargs"])
|
||||
elif len(argspec["kwonlyargs"]) > 0:
|
||||
args += ", *"
|
||||
for kwonlyarg in argspec["kwonlyargs"]:
|
||||
args += ", {}".format(kwonlyarg)
|
||||
if kwonlyarg in argspec["kwonlydefaults"]:
|
||||
if argspec["kwonlydefaults"][kwonlyarg] == Ellipsis:
|
||||
args += "=..."
|
||||
else:
|
||||
args += "={}".format(argspec["kwonlydefaults"][kwonlyarg])
|
||||
if argspec["varkw"] is not None:
|
||||
args += ", **{}".format(argspec["varkw"])
|
||||
print("{}({})".format(name, args))
|
||||
if docstring is not None:
|
||||
print(textwrap.indent(docstring, " "))
|
||||
print()
|
||||
|
||||
|
||||
def call_method(remote, method_name, args):
|
||||
method = getattr(remote, method_name)
|
||||
if args != []:
|
||||
args = eval(" ".join(args))
|
||||
try:
|
||||
iter(args)
|
||||
except TypeError:
|
||||
# not iterable
|
||||
ret = method(args)
|
||||
else:
|
||||
# iterable
|
||||
ret = method(*args)
|
||||
else:
|
||||
ret = method()
|
||||
if ret is not None:
|
||||
print("{}".format(ret))
|
||||
|
||||
|
||||
def main():
|
||||
args = get_argparser().parse_args()
|
||||
|
||||
remote = Client(args.server, args.port, None)
|
||||
|
||||
targets, id_parameters = remote.get_rpc_id()
|
||||
|
||||
if args.action != "list-targets":
|
||||
# If no target specified and remote has only one, then use this one.
|
||||
# Exit otherwise.
|
||||
if len(targets) > 1 and args.target is None:
|
||||
print("Remote server has several targets, please supply one with "
|
||||
"-t")
|
||||
sys.exit(1)
|
||||
elif args.target is None:
|
||||
args.target = targets[0]
|
||||
remote.select_rpc_target(args.target)
|
||||
|
||||
if args.action == "list-targets":
|
||||
list_targets(targets, id_parameters)
|
||||
elif args.action == "list-methods":
|
||||
list_methods(remote)
|
||||
elif args.action == "call":
|
||||
call_method(remote, args.method, args.args)
|
||||
else:
|
||||
print("Unrecognized action: {}".format(args.action))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -18,6 +18,7 @@ import traceback
|
|||
import threading
|
||||
import time
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
from artiq.protocols import pyon
|
||||
from artiq.protocols.asyncio_server import AsyncioServer as _AsyncioServer
|
||||
|
@ -127,9 +128,8 @@ class Client:
|
|||
buf += more.decode()
|
||||
return pyon.decode(buf)
|
||||
|
||||
def __do_rpc(self, name, args, kwargs):
|
||||
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
|
||||
self.__send(obj)
|
||||
def __do_action(self, action):
|
||||
self.__send(action)
|
||||
|
||||
obj = self.__recv()
|
||||
if obj["status"] == "ok":
|
||||
|
@ -139,6 +139,14 @@ class Client:
|
|||
else:
|
||||
raise ValueError
|
||||
|
||||
def __do_rpc(self, name, args, kwargs):
|
||||
obj = {"action": "call", "name": name, "args": args, "kwargs": kwargs}
|
||||
return self.__do_action(obj)
|
||||
|
||||
def get_rpc_method_list(self):
|
||||
obj = {"action": "get_rpc_method_list"}
|
||||
return self.__do_action(obj)
|
||||
|
||||
def __getattr__(self, name):
|
||||
def proxy(*args, **kwargs):
|
||||
return self.__do_rpc(name, args, kwargs)
|
||||
|
@ -397,9 +405,24 @@ class Server(_AsyncioServer):
|
|||
break
|
||||
obj = pyon.decode(line.decode())
|
||||
try:
|
||||
method = getattr(target, obj["name"])
|
||||
ret = method(*obj["args"], **obj["kwargs"])
|
||||
obj = {"status": "ok", "ret": ret}
|
||||
if obj["action"] == "get_rpc_method_list":
|
||||
members = inspect.getmembers(target, inspect.ismethod)
|
||||
methods = {}
|
||||
for name, method in members:
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
method = getattr(target, name)
|
||||
argspec = inspect.getfullargspec(method)
|
||||
methods[name] = (dict(argspec.__dict__),
|
||||
inspect.getdoc(method))
|
||||
obj = {"status": "ok", "ret": methods}
|
||||
elif obj["action"] == "call":
|
||||
method = getattr(target, obj["name"])
|
||||
ret = method(*obj["args"], **obj["kwargs"])
|
||||
obj = {"status": "ok", "ret": ret}
|
||||
else:
|
||||
raise ValueError("Unknown action: {}"
|
||||
.format(obj["action"]))
|
||||
except Exception:
|
||||
obj = {"status": "failed",
|
||||
"message": traceback.format_exc()}
|
||||
|
|
|
@ -11,7 +11,7 @@ build:
|
|||
string: dev
|
||||
entry_points:
|
||||
- artiq_client = artiq.frontend.artiq_client:main
|
||||
- artiq_ctlid = artiq.frontend.artiq_ctlid:main
|
||||
- artiq_rpctool = artiq.frontend.artiq_rpctool:main
|
||||
- artiq_gui = artiq.frontend.artiq_gui:main # [not win]
|
||||
- artiq_master = artiq.frontend.artiq_master:main
|
||||
- artiq_run = artiq.frontend.artiq_run:main
|
||||
|
|
|
@ -8,9 +8,72 @@ Local running tool
|
|||
:ref: artiq.frontend.artiq_run.get_argparser
|
||||
:prog: artiq_run
|
||||
|
||||
Controller identification tool
|
||||
Remote Procedure Call tool
|
||||
------------------------------
|
||||
|
||||
.. argparse::
|
||||
:ref: artiq.frontend.artiq_ctlid.get_argparser
|
||||
:prog: artiq_ctlid
|
||||
:ref: artiq.frontend.artiq_rpctool.get_argparser
|
||||
:prog: artiq_rpctool
|
||||
|
||||
This tool is the preferred way of handling simple ARTIQ controllers.
|
||||
Instead of writing a client for very simple cases you can just use this tool
|
||||
in order to call remote functions of an ARTIQ controller.
|
||||
|
||||
* Listing existing targets
|
||||
|
||||
The ``list-targets`` sub-command will print to standard output the
|
||||
target list of the remote server::
|
||||
|
||||
$ artiq_rpctool.py hostname port list-targets
|
||||
|
||||
* Listing callable functions
|
||||
|
||||
The ``list-methods`` sub-command will print to standard output a sorted
|
||||
list of the functions you can call on the remote server's target.
|
||||
|
||||
The list will contain function names, signatures (arguments) and
|
||||
docstrings.
|
||||
|
||||
If the server has only one target, you can do::
|
||||
|
||||
$ artiq_rpctool.py hostname port list-methods
|
||||
|
||||
Otherwise you need to specify the target, using the ``-t target``
|
||||
option::
|
||||
|
||||
$ artiq_rpctool.py hostname port list-methods -t target_name
|
||||
|
||||
* Remotely calling a function
|
||||
|
||||
The ``call`` sub-command will call a function on the specified remote
|
||||
server's target, passing the specified arguments.
|
||||
Like with the previous sub-command, you only need to provide the target
|
||||
name (with ``-t target``) if the server hosts several targets.
|
||||
|
||||
The following example will call the ``set_attenuation`` method of the
|
||||
Lda controller with the argument ``5``::
|
||||
|
||||
$ artiq_rpctool.py ::1 3253 call -t lda set_attenuation 5
|
||||
|
||||
In general, to call a function named ``f`` with N arguments named
|
||||
respectively ``x1, x2, ..., xN``.
|
||||
|
||||
You must pass them as a Python iterable object::
|
||||
|
||||
$ artiq_rpctool.py hostname port call -t target f '(x1, x2, ..., xN)'
|
||||
|
||||
You can use Python syntax to compute arguments as they will be passed
|
||||
to the ``eval()`` primitive::
|
||||
|
||||
$ artiq_rpctool.py hostname port call -t target f '(x*3+5 for x in range(8))'
|
||||
$ artiq_rpctool.py hostname port call -t target f 'range(5)'
|
||||
|
||||
If you only need one argument, you don't need to pass an iterable, a
|
||||
single value is accepted.
|
||||
|
||||
If the called function has a return value, it will get printed to
|
||||
the standard output if the value is not None like in the standard
|
||||
python interactive console::
|
||||
|
||||
$ artiq_rpctool.py ::1 3253 call get_attenuation
|
||||
5.0 dB
|
||||
|
|
|
@ -51,9 +51,9 @@ and verify that you can connect to the TCP port: ::
|
|||
|
||||
:tip: Use the key combination Ctrl-AltGr-9 to get the ``telnet>`` prompt, and enter ``close`` to quit Telnet. Quit the controller with Ctrl-C.
|
||||
|
||||
Also verify that a target (service) named "hello" (as passed in the first argument to ``simple_server_loop``) exists using the ``artiq_ctlid.py`` program from the ARTIQ front-end tools: ::
|
||||
Also verify that a target (service) named "hello" (as passed in the first argument to ``simple_server_loop``) exists using the ``artiq_rpctool.py`` program from the ARTIQ front-end tools: ::
|
||||
|
||||
$ artiq_ctlid.py ::1 3249
|
||||
$ artiq_rpctool.py ::1 3249 list-targets
|
||||
Target(s): hello
|
||||
|
||||
The client
|
||||
|
|
2
setup.py
2
setup.py
|
@ -10,7 +10,7 @@ requirements = [
|
|||
]
|
||||
scripts = [
|
||||
"artiq_client=artiq.frontend.artiq_client:main",
|
||||
"artiq_ctlid=artiq.frontend.artiq_ctlid:main",
|
||||
"artiq_rpctool=artiq.frontend.artiq_rpctool:main",
|
||||
"artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main",
|
||||
"artiq_master=artiq.frontend.artiq_master:main",
|
||||
"artiq_run=artiq.frontend.artiq_run:main",
|
||||
|
|
Loading…
Reference in New Issue