|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +# Started by François-Xavier Bourlet <[email protected]>, Jan 2012. |
| 4 | + |
| 5 | +import argparse |
| 6 | +import json |
| 7 | +import sys |
| 8 | +import inspect |
| 9 | +from pprint import pprint |
| 10 | + |
| 11 | +import zerorpc |
| 12 | + |
| 13 | + |
| 14 | +parser = argparse.ArgumentParser( |
| 15 | + description='Make a zerorpc call to a remote service.' |
| 16 | + ) |
| 17 | + |
| 18 | +client_or_server = parser.add_mutually_exclusive_group() |
| 19 | +client_or_server.add_argument('--client', action='store_true', default=True, |
| 20 | + help='remote procedure call mode (default)') |
| 21 | +client_or_server.add_argument('--server', action='store_false', dest='client', |
| 22 | + help='turn a given python module into a server') |
| 23 | + |
| 24 | +parser.add_argument('--connect', action='append', metavar='address', |
| 25 | + help='specify address to connect to. Can be specified \ |
| 26 | + multiple times and in conjunction with --bind') |
| 27 | +parser.add_argument('--bind', action='append', metavar='address', |
| 28 | + help='specify address to listen to. Can be specified \ |
| 29 | + multiple times and in conjunction with --connect') |
| 30 | +parser.add_argument('--timeout', default=30, metavar='seconds', type=int, |
| 31 | + help='abort request after X seconds. \ |
| 32 | + (default: 30s, --client only)') |
| 33 | +parser.add_argument('--heartbeat', default=5, metavar='seconds', type=int, |
| 34 | + help='heartbeat frequency. You should always use \ |
| 35 | + the same frequency as the server. (default: 5s)') |
| 36 | +parser.add_argument('-j', '--json', default=False, action='store_true', |
| 37 | + help='arguments are in JSON format and will be be parsed \ |
| 38 | + before being sent to the remote') |
| 39 | +parser.add_argument('-pj', '--print-json', default=False, action='store_true', |
| 40 | + help='print result in JSON format.') |
| 41 | +parser.add_argument('-?', '--inspect', default=False, action='store_true', |
| 42 | + help='retrieve detailed informations for the given \ |
| 43 | + remote (cf: command) method. If not method, display \ |
| 44 | + a list of remote methods signature. (only for --client).') |
| 45 | +parser.add_argument('--active-hb', default=False, action='store_true', |
| 46 | + help='enable active heartbeat. The default is to \ |
| 47 | + wait for the server to send the first heartbeat') |
| 48 | +parser.add_argument('address', nargs='?', help='address to connect to. Skip \ |
| 49 | + this if you specified --connect or --bind at least once') |
| 50 | +parser.add_argument('command', nargs='?', |
| 51 | + help='remote procedure to call if --client (default) or \ |
| 52 | + python module/class to load if --server. If no command is \ |
| 53 | + specified, a list of remote methods are displayed.') |
| 54 | +parser.add_argument('params', nargs='*', |
| 55 | + help='parameters for the remote call if --client (default)') |
| 56 | + |
| 57 | + |
| 58 | +def setup_links(args, socket): |
| 59 | + if args.bind: |
| 60 | + for endpoint in args.bind: |
| 61 | + print 'binding to "{0}"'.format(endpoint) |
| 62 | + socket.bind(endpoint) |
| 63 | + addresses = [] |
| 64 | + if args.address: |
| 65 | + addresses.append(args.address) |
| 66 | + if args.connect: |
| 67 | + addresses.extend(args.connect) |
| 68 | + for endpoint in addresses: |
| 69 | + print 'connecting to "{0}"'.format(endpoint) |
| 70 | + socket.connect(endpoint) |
| 71 | + |
| 72 | +def run_server(args): |
| 73 | + server_obj_path = args.command |
| 74 | + |
| 75 | + if '.' in server_obj_path: |
| 76 | + modulepath, objname = server_obj_path.rsplit('.', 1) |
| 77 | + module = __import__(modulepath, fromlist=[objname]) |
| 78 | + server_obj = getattr(module, objname) |
| 79 | + else: |
| 80 | + server_obj = __import__(server_obj_path) |
| 81 | + |
| 82 | + if callable(server_obj): |
| 83 | + server_obj = server_obj() |
| 84 | + |
| 85 | + server = zerorpc.Server(server_obj, heartbeat=args.heartbeat) |
| 86 | + setup_links(args, server) |
| 87 | + print 'serving "{0}"'.format(server_obj_path) |
| 88 | + return server.run() |
| 89 | + |
| 90 | +# this function does a really intricate job to keep backward compatibility |
| 91 | +# with a previous version of zerorpc, and lazily retrieving results if possible... |
| 92 | +def zerorpc_inspect(client, method=None, long_doc=True, include_argspec=True): |
| 93 | + try: |
| 94 | + remote_detailled_methods = client._zerorpc_inspect(method, |
| 95 | + long_doc)['methods'] |
| 96 | + |
| 97 | + if include_argspec: |
| 98 | + r = [(name + (inspect.formatargspec(*argspec) if argspec else |
| 99 | + '(...)'), doc if doc else '<undocumented>') for name, argspec, doc |
| 100 | + in remote_detailled_methods] |
| 101 | + else: |
| 102 | + r = [(name, doc if doc else '<undocumented>') |
| 103 | + for name, argspec, doc in remote_detailled_methods] |
| 104 | + |
| 105 | + longest_name_len = max(len(name) for name, doc in r) |
| 106 | + return (longest_name_len, r) |
| 107 | + except zerorpc.RemoteError: |
| 108 | + pass |
| 109 | + |
| 110 | + if method is None: |
| 111 | + remote_methods = client._zerorpc_list() |
| 112 | + else: |
| 113 | + remote_methods = [method] |
| 114 | + |
| 115 | + def remote_detailled_methods(): |
| 116 | + for name in remote_methods: |
| 117 | + if include_argspec: |
| 118 | + argspec = client._zerorpc_args(name) |
| 119 | + else: |
| 120 | + argspec = None |
| 121 | + docstring = client._zerorpc_help(name) |
| 122 | + if docstring and not long_doc: |
| 123 | + docstring = docstring.split('\n', 1)[0] |
| 124 | + yield (name, argspec, docstring if docstring else '<undocumented>') |
| 125 | + |
| 126 | + if not include_argspec: |
| 127 | + longest_name_len = max(len(name) for name in remote_methods) |
| 128 | + return (longest_name_len, ((name, doc) for name, argspec, doc in |
| 129 | + remote_detailled_methods())) |
| 130 | + |
| 131 | + r = [(name + (inspect.formatargspec(*argspec) if argspec else '(...)'), doc) |
| 132 | + for name, argspec, doc in remote_detailled_methods()] |
| 133 | + longest_name_len = max(len(name) for name, doc in r) |
| 134 | + return (longest_name_len, r) |
| 135 | + |
| 136 | +def run_client(args): |
| 137 | + client = zerorpc.Client(timeout=args.timeout, heartbeat=args.heartbeat, |
| 138 | + passive_heartbeat=not args.active_hb) |
| 139 | + setup_links(args, client) |
| 140 | + if not args.command: |
| 141 | + (longest_name_len, detailled_methods) = zerorpc_inspect(client, |
| 142 | + long_doc=False, include_argspec=args.inspect) |
| 143 | + if args.inspect: |
| 144 | + for (name, doc) in detailled_methods: |
| 145 | + print name |
| 146 | + else: |
| 147 | + for (name, doc) in detailled_methods: |
| 148 | + print '{0} {1}'.format(name.ljust(longest_name_len), doc) |
| 149 | + return |
| 150 | + if args.inspect: |
| 151 | + (longest_name_len, detailled_methods) = zerorpc_inspect(client, |
| 152 | + method=args.command) |
| 153 | + (name, doc) = detailled_methods[0] |
| 154 | + print '\n{0}\n\n{1}\n'.format(name, doc) |
| 155 | + return |
| 156 | + if args.json: |
| 157 | + call_args = [json.loads(x) for x in args.params] |
| 158 | + else: |
| 159 | + call_args = args.params |
| 160 | + results = client(args.command, *call_args) |
| 161 | + if getattr(results, 'next', None) is None: |
| 162 | + if args.print_json: |
| 163 | + json.dump(results, sys.stdout) |
| 164 | + else: |
| 165 | + pprint(results) |
| 166 | + else: |
| 167 | + # streaming responses |
| 168 | + if args.print_json: |
| 169 | + first = True |
| 170 | + sys.stdout.write('[') |
| 171 | + for result in results: |
| 172 | + if first: |
| 173 | + first = False |
| 174 | + else: |
| 175 | + sys.stdout.write(',') |
| 176 | + json.dump(result, sys.stdout) |
| 177 | + sys.stdout.write(']') |
| 178 | + else: |
| 179 | + for result in results: |
| 180 | + pprint(result) |
| 181 | + |
| 182 | +def main(): |
| 183 | + args = parser.parse_args() |
| 184 | + if args.bind or args.connect: |
| 185 | + if args.command: |
| 186 | + args.arguments.prepend(args.command) |
| 187 | + args.command = args.address |
| 188 | + args.address = None |
| 189 | + if args.client: |
| 190 | + return run_client(args) |
| 191 | + return run_server(args) |
| 192 | + |
| 193 | +if __name__ == '__main__': |
| 194 | + exit(main()) |
0 commit comments