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.18 2006/12/15 18:36:16 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
84 except Exception, err:
85 # Try connecting to the API server via XML-RPC
86 self.api = PLCAPI(None)
89 self.config = Config()
92 self.config = Config(config)
93 except Exception, err:
94 # 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
109 self.server = xmlrpclib.ServerProxy(url, PyCurlTransport(url, cacert), allow_none = 1)
111 # Set up authentication structure
113 # Default is to use capability authentication
114 if (method, user, password) == (None, None, None):
115 method = "capability"
117 if method == "capability":
118 # Load defaults from configuration file if using capability
120 if user is None and self.config is not None:
121 user = self.config.PLC_API_MAINTENANCE_USER
122 if password is None and self.config is not None:
123 password = self.config.PLC_API_MAINTENANCE_PASSWORD
127 # Otherwise, default to password authentication
130 if role == "anonymous" or method == "anonymous":
131 self.auth = {'AuthMethod': "anonymous"}
134 raise Exception, "Must specify username"
137 raise Exception, "Must specify password"
139 self.auth = {'AuthMethod': method,
141 'AuthString': password}
144 self.auth['Role'] = role
146 for method in PLC.Methods.methods:
147 api_function = self.api.callable(method)
149 if self.server is None:
150 # Can just call it directly
153 func = getattr(self.server, method)
155 # If the function requires an authentication structure as
156 # its first argument, automagically add an auth struct to
158 if api_function.accepts and \
159 (isinstance(api_function.accepts[0], Auth) or \
160 (isinstance(api_function.accepts[0], Mixed) and \
161 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
166 callable = Callable(self, method, func, auth)
168 # Add to ourself and the global environment. Add dummy
169 # subattributes to support tab completion of methods with
170 # dots in their names (e.g., system.listMethods).
172 paths = method.split(".")
176 if not hasattr(self, first):
178 setattr(self, first, obj)
179 # Also add to global environment if specified
180 if globals is not None:
183 obj = getattr(self, first)
186 if not hasattr(obj, path):
187 if path == paths[-1]:
188 setattr(obj, path, callable)
190 setattr(obj, path, Dummy())
191 obj = getattr(obj, path)
193 setattr(self, method, callable)
194 # Also add to global environment if specified
195 if globals is not None:
196 globals[method] = callable
198 # Override help(), begin(), and commit()
199 if globals is not None:
200 globals['help'] = self.help
201 globals['begin'] = self.begin
202 globals['commit'] = self.commit
208 def help(self, topic = None):
209 if isinstance(topic, Callable):
210 pydoc.pager(self.system.methodHelp(topic.name))
216 raise Exception, "multicall already in progress"
224 results = self.system.multicall(self.calls)
225 for result in results:
226 if type(result) == type({}):
227 raise xmlrpclib.Fault(result['faultCode'], result['faultString'])
228 elif type(result) == type([]):
229 ret.append(result[0])
231 raise ValueError, "unexpected type in multicall result"