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.2 2007/01/10 21:04:40 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 if cacert is not None:
111 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
113 self.server = xmlrpclib.ServerProxy(url, allow_none = 1)
115 # Set up authentication structure
117 # Default is to use capability authentication
118 if (method, user, password) == (None, None, None):
119 method = "capability"
121 if method == "capability":
122 # Load defaults from configuration file if using capability
124 if user is None and self.config is not None:
125 user = self.config.PLC_API_MAINTENANCE_USER
126 if password is None and self.config is not None:
127 password = self.config.PLC_API_MAINTENANCE_PASSWORD
131 # Otherwise, default to password authentication
134 if role == "anonymous" or method == "anonymous":
135 self.auth = {'AuthMethod': "anonymous"}
138 raise Exception, "Must specify username"
141 raise Exception, "Must specify password"
143 self.auth = {'AuthMethod': method,
145 'AuthString': password}
148 self.auth['Role'] = role
150 for method in PLC.Methods.methods:
151 api_function = self.api.callable(method)
153 if self.server is None:
154 # Can just call it directly
157 func = getattr(self.server, method)
159 # If the function requires an authentication structure as
160 # its first argument, automagically add an auth struct to
162 if api_function.accepts and \
163 (isinstance(api_function.accepts[0], Auth) or \
164 (isinstance(api_function.accepts[0], Mixed) and \
165 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
170 callable = Callable(self, method, func, auth)
172 # Add to ourself and the global environment. Add dummy
173 # subattributes to support tab completion of methods with
174 # dots in their names (e.g., system.listMethods).
176 paths = method.split(".")
180 if not hasattr(self, first):
182 setattr(self, first, obj)
183 # Also add to global environment if specified
184 if globals is not None:
187 obj = getattr(self, first)
190 if not hasattr(obj, path):
191 if path == paths[-1]:
192 setattr(obj, path, callable)
194 setattr(obj, path, Dummy())
195 obj = getattr(obj, path)
197 setattr(self, method, callable)
198 # Also add to global environment if specified
199 if globals is not None:
200 globals[method] = callable
202 # Override help(), begin(), and commit()
203 if globals is not None:
204 globals['help'] = self.help
205 globals['begin'] = self.begin
206 globals['commit'] = self.commit
212 def help(self, topic = None):
213 if isinstance(topic, Callable):
214 pydoc.pager(self.system.methodHelp(topic.name))
220 raise Exception, "multicall already in progress"
228 results = self.system.multicall(self.calls)
229 for result in results:
230 if type(result) == type({}):
231 raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
232 elif type(result) == type([]):
233 ret.append(result[0])
235 raise ValueError, "unexpected type in multicall result"