Package foolscap :: Module remoteinterface
[hide private]
[frames] | no frames]

Source Code for Module foolscap.remoteinterface

  1   
  2  import types, inspect 
  3  from zope.interface import interface, providedBy, implements 
  4  from foolscap.constraint import Constraint, OpenerConstraint, nothingTaster, \ 
  5       IConstraint, UnboundedSchema, IRemoteMethodConstraint, Optional, Any 
  6  from foolscap.tokens import Violation, InvalidRemoteInterface 
  7  from foolscap.schema import addToConstraintTypeMap 
  8  from foolscap import ipb 
  9   
10 -class RemoteInterfaceClass(interface.InterfaceClass):
11 """This metaclass lets RemoteInterfaces be a lot like Interfaces. The 12 methods are parsed differently (PB needs more information from them than 13 z.i extracts, and the methods can be specified with a RemoteMethodSchema 14 directly). 15 16 RemoteInterfaces can accept the following additional attribute:: 17 18 __remote_name__: can be set to a string to specify the globally-unique 19 name for this interface. This should be a URL in a 20 namespace you administer. If not set, defaults to the 21 short classname. 22 23 RIFoo.names() returns the list of remote method names. 24 25 RIFoo['bar'] is still used to get information about method 'bar', however 26 it returns a RemoteMethodSchema instead of a z.i Method instance. 27 28 """ 29
30 - def __init__(self, iname, bases=(), attrs=None, __module__=None):
31 if attrs is None: 32 interface.InterfaceClass.__init__(self, iname, bases, attrs, 33 __module__) 34 return 35 36 # parse (and remove) the attributes that make this a RemoteInterface 37 try: 38 rname, remote_attrs = self._parseRemoteInterface(iname, attrs) 39 except: 40 raise 41 42 # now let the normal InterfaceClass do its thing 43 interface.InterfaceClass.__init__(self, iname, bases, attrs, 44 __module__) 45 46 # now add all the remote methods that InterfaceClass would have 47 # complained about. This is really gross, and it really makes me 48 # question why we're bothing to inherit from z.i.Interface at all. I 49 # will probably stop doing that soon, and just have our own 50 # meta-class, but I want to make sure you can still do 51 # 'implements(RIFoo)' from within a class definition. 52 53 a = getattr(self, "_InterfaceClass__attrs") # the ickiest part 54 a.update(remote_attrs) 55 self.__remote_name__ = rname 56 57 # finally, auto-register the interface 58 try: 59 registerRemoteInterface(self, rname) 60 except: 61 raise
62
63 - def _parseRemoteInterface(self, iname, attrs):
64 remote_attrs = {} 65 66 remote_name = attrs.get("__remote_name__", iname) 67 68 # and see if there is a __remote_name__ . We delete it because 69 # InterfaceClass doesn't like arbitrary attributes 70 if attrs.has_key("__remote_name__"): 71 del attrs["__remote_name__"] 72 73 # determine all remotely-callable methods 74 names = [name for name in attrs.keys() 75 if ((type(attrs[name]) == types.FunctionType and 76 not name.startswith("_")) or 77 IConstraint.providedBy(attrs[name]))] 78 79 # turn them into constraints. Tag each of them with their name and 80 # the RemoteInterface they came from. 81 for name in names: 82 m = attrs[name] 83 if not IConstraint.providedBy(m): 84 m = RemoteMethodSchema(method=m) 85 m.name = name 86 m.interface = self 87 remote_attrs[name] = m 88 # delete the methods, so zope's InterfaceClass doesn't see them. 89 # Particularly necessary for things defined with IConstraints. 90 del attrs[name] 91 92 return remote_name, remote_attrs
93 94 RemoteInterface = RemoteInterfaceClass("RemoteInterface", 95 __module__="pb.flavors") 96 97 98
99 -def getRemoteInterface(obj):
100 """Get the (one) RemoteInterface supported by the object, or None.""" 101 interfaces = list(providedBy(obj)) 102 # TODO: versioned Interfaces! 103 ilist = [] 104 for i in interfaces: 105 if isinstance(i, RemoteInterfaceClass): 106 if i not in ilist: 107 ilist.append(i) 108 assert len(ilist) <= 1, ("don't use multiple RemoteInterfaces! %s uses %s" 109 % (obj, ilist)) 110 if ilist: 111 return ilist[0] 112 return None
113
114 -class DuplicateRemoteInterfaceError(Exception):
115 pass
116 117 RemoteInterfaceRegistry = {}
118 -def registerRemoteInterface(iface, name=None):
119 if not name: 120 name = iface.__remote_name__ 121 assert isinstance(iface, RemoteInterfaceClass) 122 if RemoteInterfaceRegistry.has_key(name): 123 old = RemoteInterfaceRegistry[name] 124 msg = "remote interface %s was registered with the same name (%s) as %s, please use __remote_name__ to provide a unique name" % (old, name, iface) 125 raise DuplicateRemoteInterfaceError(msg) 126 RemoteInterfaceRegistry[name] = iface
127
128 -def getRemoteInterfaceByName(iname):
129 return RemoteInterfaceRegistry.get(iname)
130 131 132
133 -class RemoteMethodSchema:
134 """ 135 This is a constraint for a single remotely-invokable method. It gets to 136 require, deny, or impose further constraints upon a set of named 137 arguments. 138 139 This constraint is created by using keyword arguments with the same 140 names as the target method's arguments. Two special names are used: 141 142 __ignoreUnknown__: if True, unexpected argument names are silently 143 dropped. (note that this makes the schema unbounded) 144 145 __acceptUnknown__: if True, unexpected argument names are always 146 accepted without a constraint (which also makes this schema unbounded) 147 148 The remotely-accesible object's .getMethodSchema() method may return one 149 of these objects. 150 """ 151 152 implements(IRemoteMethodConstraint) 153 154 taster = {} # this should not be used as a top-level constraint 155 opentypes = [] # overkill 156 ignoreUnknown = False 157 acceptUnknown = False 158 159 name = None # method name, set when the RemoteInterface is parsed 160 interface = None # points to the RemoteInterface which defines the method 161 162 # under development
163 - def __init__(self, method=None, _response=None, __options=[], **kwargs):
164 if method: 165 self.initFromMethod(method) 166 return 167 self.argumentNames = [] 168 self.argConstraints = {} 169 self.required = [] 170 self.responseConstraint = None 171 # __response in the argslist gets treated specially, I think it is 172 # mangled into _RemoteMethodSchema__response or something. When I 173 # change it to use _response instead, it works. 174 if _response: 175 self.responseConstraint = IConstraint(_response) 176 self.options = {} # return, wait, reliable, etc 177 178 if kwargs.has_key("__ignoreUnknown__"): 179 self.ignoreUnknown = kwargs["__ignoreUnknown__"] 180 del kwargs["__ignoreUnknown__"] 181 if kwargs.has_key("__acceptUnknown__"): 182 self.acceptUnknown = kwargs["__acceptUnknown__"] 183 del kwargs["__acceptUnknown__"] 184 185 for argname, constraint in kwargs.items(): 186 self.argumentNames.append(argname) 187 constraint = IConstraint(constraint) 188 self.argConstraints[argname] = constraint 189 if not isinstance(constraint, Optional): 190 self.required.append(argname)
191
192 - def initFromMethod(self, method):
193 # call this with the Interface's prototype method: the one that has 194 # argument constraints expressed as default arguments, and which 195 # does nothing but returns the appropriate return type 196 197 names, _, _, typeList = inspect.getargspec(method) 198 if names and names[0] == 'self': 199 why = "RemoteInterface methods should not have 'self' in their argument list" 200 raise InvalidRemoteInterface(why) 201 if not names: 202 typeList = [] 203 # 'def foo(oops)' results in typeList==None 204 if typeList is None or len(names) != len(typeList): 205 # TODO: relax this, use schema=Any for the args that don't have 206 # default values. This would make: 207 # def foo(a, b=int): return None 208 # equivalent to: 209 # def foo(a=Any, b=int): return None 210 why = "RemoteInterface methods must have default values for all their arguments" 211 raise InvalidRemoteInterface(why) 212 self.argumentNames = names 213 self.argConstraints = {} 214 self.required = [] 215 for i in range(len(names)): 216 argname = names[i] 217 constraint = typeList[i] 218 if not isinstance(constraint, Optional): 219 self.required.append(argname) 220 self.argConstraints[argname] = IConstraint(constraint) 221 222 # call the method, its 'return' value is the return constraint 223 self.responseConstraint = IConstraint(method()) 224 self.options = {} # return, wait, reliable, etc
225 226
227 - def getPositionalArgConstraint(self, argnum):
228 if argnum >= len(self.argumentNames): 229 raise Violation("too many positional arguments: %d >= %d" % 230 (argnum, len(self.argumentNames))) 231 argname = self.argumentNames[argnum] 232 c = self.argConstraints.get(argname) 233 assert c 234 if isinstance(c, Optional): 235 c = c.constraint 236 return (True, c)
237
238 - def getKeywordArgConstraint(self, argname, 239 num_posargs=0, previous_kwargs=[]):
240 previous_args = self.argumentNames[:num_posargs] 241 for pkw in previous_kwargs: 242 assert pkw not in previous_args 243 previous_args.append(pkw) 244 if argname in previous_args: 245 raise Violation("got multiple values for keyword argument '%s'" 246 % (argname,)) 247 c = self.argConstraints.get(argname) 248 if c: 249 if isinstance(c, Optional): 250 c = c.constraint 251 return (True, c) 252 # what do we do with unknown argumen