3 # Interactive shell for testing PLCAPI
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
8 # $Id: Shell.py,v 1.1 2007/01/08 18:10:30 mlhuang Exp $
14 from PLC.API import PLCAPI
15 from PLC.Parameter import Mixed
16 from PLC.Auth import Auth
17 from PLC.Config import Config
18 from PLC.Method import Method
19 from PLC.PyCurl import PyCurlTransport
24 Wrapper to call a method either directly or remotely and
25 automagically add the authentication structure if necessary.
28 def __init__(self, shell, name, func, auth = None):
34 def __call__(self, *args, **kwds):
36 Automagically add the authentication structure if the function
37 requires it and it has not been specified.
41 (not args or not isinstance(args[0], dict) or \
42 (not args[0].has_key('AuthMethod') and \
43 not args[0].has_key('session'))):
44 args = (self.auth,) + args
47 self.shell.calls.append({'methodName': self.name, 'params': list(args)})
50 return self.func(*args, **kwds)
54 # Add API functions to global scope
59 url = None, xmlrpc = False, cacert = None,
61 method = None, role = None, user = None, password = None):
63 Initialize a new shell instance. Re-initializes globals.
67 # If any XML-RPC options have been specified, do not try
68 # connecting directly to the DB.
69 if (url, method, user, password, role, cacert, xmlrpc) != \
70 (None, None, None, None, None, None, False):
73 # Otherwise, first try connecting directly to the DB. This
74 # absolutely requires a configuration file; the API
75 # instance looks for one in a default location if one is
76 # not specified. If this fails, try connecting to the API
81 self.api = PLCAPI(config)
82 self.config = self.api.config
85 except Exception, err:
86 # Try connecting to the API server via XML-RPC
87 self.api = PLCAPI(None)
91 self.config = Config()
93 self.config = Config(config)
94 except Exception, err:
95 # Try to continue if no configuration file is available
99 if self.config is None:
100 raise Exception, "Must specify API URL"
102 url = "https://" + self.config.PLC_API_HOST + \
103 ":" + str(self.config.PLC_API_PORT) + \
104 "/" + self.config.PLC_API_PATH + "/"
106 if cacert is None and self.config is not None:
107 cacert = self.config.PLC_API_CA_SSL_CRT
110 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
112 # Set up authentication structure
114 # Default is to use capability authentication
115 if (method, user, password) == (None, None, None):
116 method = "capability"
118 if method == "capability":
119 # Load defaults from configuration file if using capability
121 if user is None and self.config is not None:
122 user = self.config.PLC_API_MAINTENANCE_USER
123 if password is None and self.config is not None:
124 password = self.config.PLC_API_MAINTENANCE_PASSWORD
128 # Otherwise, default to password authentication
131 if role == "anonymous" or method == "anonymous":
132 self.auth = {'AuthMethod': "anonymous"}
135 raise Exception, "Must specify username"
138 raise Exception, "Must specify password"
140 self.auth = {'AuthMethod': method,
142 'AuthString': password}
145 self.auth['Role'] = role
147 for method in PLC.Methods.methods:
148 api_function = self.api.callable(method)
150 if self.server is None:
151 # Can just call it directly
154 func = getattr(self.server, method)
156 # If the function requires an authentication structure as
157 # its first argument, automagically add an auth struct to
159 if api_function.accepts and \
160 (isinstance(api_function.accepts[0], Auth) or \
161 (isinstance(api_function.accepts[0], Mixed) and \
162 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
167 callable = Callable(self, method, func, auth)
169 # Add to ourself and the global environment. Add dummy
170 # subattributes to support tab completion of methods with
171 # dots in their names (e.g., system.listMethods).
173 paths = method.split(".")
177 if not hasattr(self, first):
179 setattr(self, first, obj)
180 # Also add to global environment if specified
181 if globals is not None:
184 obj = getattr(self, first)
187 if not hasattr(obj, path):
188 if path == paths[-1]:
189 setattr(obj, path, callable)
191 setattr(obj, path, Dummy())
192 obj = getattr(obj, path)
194 setattr(self, method, callable)
195 # Also add to global environment if specified
196 if globals is not None:
197 globals[method] = callable
199 # Override help(), begin(), and commit()
200 if globals is not None:
201 globals['help'] = self.help
202 globals['begin'] = self.begin
203 globals['commit'] = self.commit
209 def help(self, topic = None):
210 if isinstance(topic, Callable):
211 pydoc.pager(self.system.methodHelp(topic.name))
217 raise Exception, "multicall already in progress"
225 results = self.system.multicall(self.calls)
226 for result in results:
227 if type(result) == type({}):
228 raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
229 elif type(result) == type([]):
230 ret.append(result[0])
232 raise ValueError, "unexpected type in multicall result"