class SetConstraint(OpenerConstraint): """The object must be a Set of some sort, with a given maximum size. To accept sets of any size, use maxLength=None. All member objects must obey the given constraint. By default this will accept both mutable and immutable sets, if you want to require a particular type, set mutable= to either True or False. """ # TODO: if mutable!=None, we won't throw out the wrong set type soon # enough. We need to override checkOpenType to accomplish this. opentypes = [("set", ), ("immutable-set", )] name = "SetConstraint" def __init__(self, constraint, maxLength=None, mutable=None): self.constraint = IConstraint(constraint) self.maxLength = maxLength self.mutable = mutable def checkObject(self, obj, inbound): if not isinstance(obj, (set, frozenset)): raise Violation("not a set") if (self.mutable == True and not isinstance(obj, set)): raise Violation("obj is a set, but not a mutable one") if (self.mutable == False and not isinstance(obj, frozenset)): raise Violation("obj is a set, but not an immutable one") if self.maxLength is not None and len(obj) > self.maxLength: raise Violation("set is too large") if self.constraint: for o in obj: self.constraint.checkObject(o, inbound)
class ListConstraint(OpenerConstraint): """The object must be a list of objects, with a given maximum length. To accept lists of any length, use maxLength=None. All member objects must obey the given constraint.""" opentypes = [(b'list', )] name = 'ListConstraint' def __init__(self, constraint, maxLength=None, minLength=0): self.constraint = IConstraint(constraint) self.maxLength = maxLength self.minLength = minLength def checkObject(self, obj, inbound): if not isinstance(obj, list): raise Violation("not a list") if self.maxLength is not None and len(obj) > self.maxLength: raise Violation("list too long") if len(obj) < self.minLength: raise Violation("list too short") for o in obj: self.constraint.checkObject(o, inbound)
class SetConstraint(OpenerConstraint): """The object must be a Set of some sort, with a given maximum size. To accept sets of any size, use maxLength=None. All member objects must obey the given constraint. By default this will accept both mutable and immutable sets, if you want to require a particular type, set mutable= to either True or False. """ # TODO: if mutable!=None, we won't throw out the wrong set type soon # enough. We need to override checkOpenType to accomplish this. opentypes = [("set",), ("immutable-set",)] name = "SetConstraint" def __init__(self, constraint, maxLength=None, mutable=None): self.constraint = IConstraint(constraint) self.maxLength = maxLength self.mutable = mutable def checkObject(self, obj, inbound): if not isinstance(obj, (set, frozenset)): raise Violation("not a set") if (self.mutable == True and not isinstance(obj, set)): raise Violation("obj is a set, but not a mutable one") if (self.mutable == False and not isinstance(obj, frozenset)): raise Violation("obj is a set, but not an immutable one") if self.maxLength is not None and len(obj) > self.maxLength: raise Violation("set is too large") if self.constraint: for o in obj: self.constraint.checkObject(o, inbound)
class DictConstraint(OpenerConstraint): opentypes = [("dict",)] name = "DictConstraint" def __init__(self, keyConstraint, valueConstraint, maxKeys=None): self.keyConstraint = IConstraint(keyConstraint) self.valueConstraint = IConstraint(valueConstraint) self.maxKeys = maxKeys def checkObject(self, obj, inbound): if not isinstance(obj, dict): raise Violation("'%s' (%s) is not a Dictionary" % (obj, type(obj))) if self.maxKeys != None and len(obj) > self.maxKeys: raise Violation("Dict keys=%d > maxKeys=%d" % (len(obj), self.maxKeys)) for key, value in obj.items(): self.keyConstraint.checkObject(key, inbound) self.valueConstraint.checkObject(value, inbound)
class DictConstraint(OpenerConstraint): opentypes = [("dict",)] name = "DictConstraint" def __init__(self, keyConstraint, valueConstraint, maxKeys=None): self.keyConstraint = IConstraint(keyConstraint) self.valueConstraint = IConstraint(valueConstraint) self.maxKeys = maxKeys def checkObject(self, obj, inbound): if not isinstance(obj, dict): raise Violation, "'%s' (%s) is not a Dictionary" % (obj, type(obj)) if self.maxKeys != None and len(obj) > self.maxKeys: raise Violation, "Dict keys=%d > maxKeys=%d" % (len(obj), self.maxKeys) for key, value in obj.iteritems(): self.keyConstraint.checkObject(key, inbound) self.valueConstraint.checkObject(value, inbound)
class RemoteMethodSchema(object): """ This is a constraint for a single remotely-invokable method. It gets to require, deny, or impose further constraints upon a set of named arguments. This constraint is created by using keyword arguments with the same names as the target method's arguments. Two special names are used: __ignoreUnknown__: if True, unexpected argument names are silently dropped. (note that this makes the schema unbounded) __acceptUnknown__: if True, unexpected argument names are always accepted without a constraint (which also makes this schema unbounded) The remotely-accesible object's .getMethodSchema() method may return one of these objects. """ taster = {} # this should not be used as a top-level constraint opentypes = [] # overkill ignoreUnknown = False acceptUnknown = False name = None # method name, set when the RemoteInterface is parsed interface = None # points to the RemoteInterface which defines the method # under development def __init__(self, method=None, _response=None, __options=[], **kwargs): if method: self.initFromMethod(method) return self.argumentNames = [] self.argConstraints = {} self.required = [] self.responseConstraint = None # __response in the argslist gets treated specially, I think it is # mangled into _RemoteMethodSchema__response or something. When I # change it to use _response instead, it works. if _response: self.responseConstraint = IConstraint(_response) self.options = {} # return, wait, reliable, etc if "__ignoreUnknown__" in kwargs: self.ignoreUnknown = kwargs["__ignoreUnknown__"] del kwargs["__ignoreUnknown__"] if "__acceptUnknown__" in kwargs: self.acceptUnknown = kwargs["__acceptUnknown__"] del kwargs["__acceptUnknown__"] for argname, constraint in list(kwargs.items()): self.argumentNames.append(argname) constraint = IConstraint(constraint) self.argConstraints[argname] = constraint if not isinstance(constraint, Optional): self.required.append(argname) def initFromMethod(self, method): # call this with the Interface's prototype method: the one that has # argument constraints expressed as default arguments, and which # does nothing but returns the appropriate return type names, _, _, typeList = inspect.getargspec(method) if names and names[0] == 'self': why = "RemoteInterface methods should not have 'self' in their argument list" raise InvalidRemoteInterface(why) if not names: typeList = [] # 'def foo(oops)' results in typeList==None if typeList is None or len(names) != len(typeList): # TODO: relax this, use schema=Any for the args that don't have # default values. This would make: # def foo(a, b=int): return None # equivalent to: # def foo(a=Any, b=int): return None why = "RemoteInterface methods must have default values for all their arguments" raise InvalidRemoteInterface(why) self.argumentNames = names self.argConstraints = {} self.required = [] for i in range(len(names)): argname = names[i] constraint = typeList[i] if not isinstance(constraint, Optional): self.required.append(argname) self.argConstraints[argname] = IConstraint(constraint) # call the method, its 'return' value is the return constraint self.responseConstraint = IConstraint(method()) self.options = {} # return, wait, reliable, etc def getPositionalArgConstraint(self, argnum): if argnum >= len(self.argumentNames): raise Violation("too many positional arguments: %d >= %d" % (argnum, len(self.argumentNames))) argname = self.argumentNames[argnum] c = self.argConstraints.get(argname) assert c if isinstance(c, Optional): c = c.constraint return (True, c) def getKeywordArgConstraint(self, argname, num_posargs=0, previous_kwargs=[]): previous_args = self.argumentNames[:num_posargs] for pkw in previous_kwargs: assert pkw not in previous_args previous_args.append(pkw) if argname in previous_args: raise Violation("got multiple values for keyword argument '%s'" % (argname, )) c = self.argConstraints.get(argname) if c: if isinstance(c, Optional): c = c.constraint return (True, c) # what do we do with unknown arguments? if self.ignoreUnknown: return (False, None) if self.acceptUnknown: return (True, None) raise Violation("unknown argument '%s'" % argname) def getResponseConstraint(self): return self.responseConstraint def checkAllArgs(self, args, kwargs, inbound): # first we map the positional arguments allargs = {} if len(args) > len(self.argumentNames): raise Violation("method takes %d positional arguments (%d given)" % (len(self.argumentNames), len(args))) for i, argvalue in enumerate(args): allargs[self.argumentNames[i]] = argvalue for argname, argvalue in list(kwargs.items()): if argname in allargs: raise Violation( "got multiple values for keyword argument '%s'" % (argname, )) allargs[argname] = argvalue for argname, argvalue in list(allargs.items()): accept, constraint = self.getKeywordArgConstraint(argname) if not accept: # this argument will be ignored by the far end. TODO: emit a # warning pass try: constraint.checkObject(argvalue, inbound) except Violation as v: v.setLocation("%s=" % argname) raise for argname in self.required: if argname not in allargs: raise Violation("missing required argument '%s'" % argname) def checkResults(self, results, inbound): if self.responseConstraint: # this might raise a Violation. The caller will annotate its # location appropriately: they have more information than we do. self.responseConstraint.checkObject(results, inbound)