Ejemplo n.º 1
0
    def _parseRemoteInterface(self, iname, attrs):
        remote_attrs = {}

        remote_name = attrs.get("__remote_name__", iname)

        # and see if there is a __remote_name__ . We delete it because
        # InterfaceClass doesn't like arbitrary attributes
        if attrs.has_key("__remote_name__"):
            del attrs["__remote_name__"]

        # determine all remotely-callable methods
        names = [
            name
            for name in attrs.keys()
            if (
                (type(attrs[name]) == types.FunctionType and not name.startswith("_"))
                or IConstraint.providedBy(attrs[name])
            )
        ]

        # turn them into constraints. Tag each of them with their name and
        # the RemoteInterface they came from.
        for name in names:
            m = attrs[name]
            if not IConstraint.providedBy(m):
                m = RemoteMethodSchema(method=m)
            m.name = name
            m.interface = self
            remote_attrs[name] = m
            # delete the methods, so zope's InterfaceClass doesn't see them.
            # Particularly necessary for things defined with IConstraints.
            del attrs[name]

        return remote_name, remote_attrs
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
def adapt_obj_to_iconstraint(iface, t):
    if iface is not IConstraint:
        return None
    assert not IConstraint.providedBy(t) # not sure about this

    c = constraintMap.get(t, None)
    if c:
        return c

    for (typ, constraintMaker) in constraintTypeMap:
        if isinstance(t, typ):
            c = constraintMaker(t)
            if c:
                return c

    # RIFoo means accept either a Referenceable that implements RIFoo, or a
    # RemoteReference that points to just such a Referenceable. This is
    # hooked in by remoteinterface.py, when it calls addToConstraintTypeMap

    # we are the only way to make constraints
    raise UnknownSchemaType("can't make constraint from '%s' (%s)" %
                            (t, type(t)))
Ejemplo n.º 5
0
 def __init__(self, *elemConstraints):
     self.constraints = [IConstraint(e) for e in elemConstraints]
Ejemplo n.º 6
0
class RemoteReference(RemoteReferenceOnly):
    def callRemote(self, _name, *args, **kwargs):
        # Note: for consistency, *all* failures are reported asynchronously.
        return defer.maybeDeferred(self._callRemote, _name, *args, **kwargs)

    def callRemoteOnly(self, _name, *args, **kwargs):
        # the remote end will not send us a response. The only error cases
        # are arguments that don't match the schema, or broken invariants. In
        # particular, DeadReferenceError will be silently consumed.
        d = defer.maybeDeferred(self._callRemote, _name, _callOnly=True,
                                *args, **kwargs)
        del d
        return None

    def _callRemote(self, _name, *args, **kwargs):
        req = None
        broker = self.tracker.broker

        # remember that "none" is not a valid constraint, so we use it to
        # mean "not set by the caller", which means we fall back to whatever
        # the RemoteInterface says. Using None would mean an AnyConstraint,
        # which is not the same thing.
        methodConstraintOverride = kwargs.get("_methodConstraint", "none")
        resultConstraint = kwargs.get("_resultConstraint", "none")
        useSchema = kwargs.get("_useSchema", True)
        callOnly = kwargs.get("_callOnly", False)

        if "_methodConstraint" in kwargs:
            del kwargs["_methodConstraint"]
        if "_resultConstraint" in kwargs:
            del kwargs["_resultConstraint"]
        if "_useSchema" in kwargs:
            del kwargs["_useSchema"]
        if "_callOnly" in kwargs:
            del kwargs["_callOnly"]

        if callOnly:
            if broker.disconnected:
                # DeadReferenceError is silently consumed
                return
            reqID = 0
        else:
            # newRequestID() could fail with a DeadReferenceError
            reqID = broker.newRequestID()

        # in this section, we validate the outbound arguments against our
        # notion of what the other end will accept (the RemoteInterface)

        # first, figure out which method they want to invoke
        (interfaceName,
         methodName,
         methodSchema) = self._getMethodInfo(_name)

        req = call.PendingRequest(reqID, self, interfaceName, methodName)
        # TODO: consider adding a stringified stack trace to that
        # PendingRequest creation, so that DeadReferenceError can emit even
        # more information about the call which failed

        # for debugging: these are put into the messages emitted when
        # logRemoteFailures is turned on
        req.interfaceName = interfaceName
        req.methodName = methodName

        if methodConstraintOverride != "none":
            methodSchema = methodConstraintOverride

        if useSchema and methodSchema:
            # check args against the arg constraint. This could fail if
            # any arguments are of the wrong type
            try:
                methodSchema.checkAllArgs(args, kwargs, False)
            except Violation, v:
                v.setLocation("%s.%s(%s)" % (interfaceName, methodName,
                                             v.getLocation()))
                raise

            # the Interface gets to constraint the return value too, so
            # make a note of it to use later
            req.setConstraint(methodSchema.getResponseConstraint())

        # if the caller specified a _resultConstraint, that overrides
        # the schema's one
        if resultConstraint != "none":
            # overrides schema
            req.setConstraint(IConstraint(resultConstraint))

        clid = self.tracker.clid
        slicer = call.CallSlicer(reqID, clid, methodName, args, kwargs)

        # up to this point, we are not committed to sending anything to the
        # far end. The various phases of commitment are:

        #  1: once we tell our broker about the PendingRequest, we must
        #  promise to retire it eventually. Specifically, if we encounter an
        #  error before we give responsibility to the connection, we must
        #  retire it ourselves.

        #  2: once we start sending the CallSlicer to the other end (in
        #  particular, once they receive the reqID), they might send us a
        #  response, so we must be prepared to handle that. Giving the
        #  PendingRequest to the broker arranges for this to happen.

        # So all failures which occur before these commitment events are
        # entirely local: stale broker, bad method name, bad arguments. If
        # anything raises an exception before this point, the PendingRequest
        # is abandoned, and our maybeDeferred wrapper returns a failing
        # Deferred.

        # commitment point 1. We assume that if this call raises an
        # exception, the broker will be sure to not track the dead
        # PendingRequest
        if not callOnly:
            broker.addRequest(req)
            # if callOnly, the PendingRequest will never know about the
            # broker, and will therefore never ask to be removed from it

        # TODO: there is a decidability problem here: if the reqID made
        # it through, the other end will send us an answer (possibly an
        # error if the remaining slices were aborted). If not, we will
        # not get an answer. To decide whether we should remove our
        # broker.waitingForAnswers[] entry, we need to know how far the
        # slicing process made it.

        try:
            # commitment point 2
            d = broker.send(slicer)
            # d will fire when the last argument has been serialized. It will
            # errback if the arguments (or any of their children) could not
            # be serialized. We need to catch this case and errback the
            # caller.

            # if we got here, we have been able to start serializing the
            # arguments. If serialization fails, the PendingRequest needs to
            # be flunked (because we aren't guaranteed that the far end will
            # do it).

            d.addErrback(req.fail)

        except:
            req.fail(failure.Failure())

        # the remote end could send back an error response for many reasons:
        #  bad method name
        #  bad argument types (violated their schema)
        #  exception during method execution
        #  method result violated the results schema
        # something else could occur to cause an errback:
        #  connection lost before response completely received
        #  exception during deserialization of the response
        #   [but only if it occurs after the reqID is received]
        #  method result violated our results schema
        # if none of those occurred, the callback will be run

        return req.deferred
Ejemplo n.º 7
0
 def __init__(self, keyConstraint, valueConstraint, maxKeys=None):
     self.keyConstraint = IConstraint(keyConstraint)
     self.valueConstraint = IConstraint(valueConstraint)
     self.maxKeys = maxKeys
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
 def __init__(self, constraint, maxLength=None, mutable=None):
     self.constraint = IConstraint(constraint)
     self.maxLength = maxLength
     self.mutable = mutable
Ejemplo n.º 10
0
 def __init__(self, *alternatives):
     self.alternatives = [IConstraint(a) for a in alternatives]
     self.alternatives = tuple(self.alternatives)
Ejemplo n.º 11
0
 def __init__(self, keyConstraint, valueConstraint, maxKeys=None):
     self.keyConstraint = IConstraint(keyConstraint)
     self.valueConstraint = IConstraint(valueConstraint)
     self.maxKeys = maxKeys
Ejemplo n.º 12
0
 def __init__(self, constraint, maxLength=None, minLength=0):
     self.constraint = IConstraint(constraint)
     self.maxLength = maxLength
     self.minLength = minLength
Ejemplo n.º 13
0
 def __init__(self, constraint, maxLength=None, mutable=None):
     self.constraint = IConstraint(constraint)
     self.maxLength = maxLength
     self.mutable = mutable