1# -*- test-case-name: foolscap.test.test_banana -*- 2 3from __future__ import print_function 4from twisted.python import log 5from twisted.internet.defer import Deferred 6from foolscap.tokens import Violation 7from foolscap.slicer import BaseSlicer, BaseUnslicer 8from foolscap.constraint import OpenerConstraint, Any, IConstraint 9from foolscap.util import AsyncAND 10 11 12class ListSlicer(BaseSlicer): 13 opentype = ("list",) 14 trackReferences = True 15 slices = list 16 17 def sliceBody(self, streamable, banana): 18 for i in self.obj: 19 yield i 20 21class ListUnslicer(BaseUnslicer): 22 opentype = ("list",) 23 24 maxLength = None 25 itemConstraint = None 26 debug = False 27 28 def setConstraint(self, constraint): 29 if isinstance(constraint, Any): 30 return 31 assert isinstance(constraint, ListConstraint) 32 self.maxLength = constraint.maxLength 33 self.itemConstraint = constraint.constraint 34 35 def start(self, count): 36 #self.opener = foo # could replace it if we wanted to 37 self.list = [] 38 self.count = count 39 if self.debug: 40 log.msg("%s[%d].start with %s" % (self, self.count, self.list)) 41 self.protocol.setObject(count, self.list) 42 self._ready_deferreds = [] 43 44 def checkToken(self, typebyte, size): 45 if self.maxLength != None and len(self.list) >= self.maxLength: 46 # list is full, no more tokens accepted 47 # this is hit if the max+1 item is a primitive type 48 raise Violation("the list is full") 49 if self.itemConstraint: 50 self.itemConstraint.checkToken(typebyte, size) 51 52 def doOpen(self, opentype): 53 # decide whether the given object type is acceptable here. Raise a 54 # Violation exception if not, otherwise give it to our opener (which 55 # will normally be the RootUnslicer). Apply a constraint to the new 56 # unslicer. 57 if self.maxLength != None and len(self.list) >= self.maxLength: 58 # this is hit if the max+1 item is a non-primitive type 59 raise Violation("the list is full") 60 if self.itemConstraint: 61 self.itemConstraint.checkOpentype(opentype) 62 unslicer = self.open(opentype) 63 if unslicer: 64 if self.itemConstraint: 65 unslicer.setConstraint(self.itemConstraint) 66 return unslicer 67 68 def update(self, obj, index): 69 # obj has already passed typechecking 70 if self.debug: 71 log.msg("%s[%d].update: [%d]=%s" % (self, self.count, index, obj)) 72 assert isinstance(index, int) 73 self.list[index] = obj 74 return obj 75 76 def receiveChild(self, obj, ready_deferred=None): 77 if ready_deferred: 78 self._ready_deferreds.append(ready_deferred) 79 if self.debug: 80 log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj)) 81 # obj could be a primitive type, a Deferred, or a complex type like 82 # those returned from an InstanceUnslicer. However, the individual 83 # object has already been through the schema validation process. The 84 # only remaining question is whether the larger schema will accept 85 # it. 86 if self.maxLength != None and len(self.list) >= self.maxLength: 87 # this is redundant 88 # (if it were a non-primitive one, it would be caught in doOpen) 89 # (if it were a primitive one, it would be caught in checkToken) 90 raise Violation("the list is full") 91 if isinstance(obj, Deferred): 92 if self.debug: 93 log.msg(" adding my update[%d] to %s" % (len(self.list), obj)) 94 obj.addCallback(self.update, len(self.list)) 95 obj.addErrback(self.printErr) 96 placeholder = "list placeholder for arg[%d], rd=%s" % \ 97 (len(self.list), ready_deferred) 98 self.list.append(placeholder) 99 else: 100 self.list.append(obj) 101 102 def printErr(self, why): 103 print("ERR!") 104 print(why.getBriefTraceback()) 105 log.err(why) 106 107 def receiveClose(self): 108 ready_deferred = None 109 if self._ready_deferreds: 110 ready_deferred = AsyncAND(self._ready_deferreds) 111 return self.list, ready_deferred 112 113 def describe(self): 114 return "[%d]" % len(self.list) 115 116 117class ListConstraint(OpenerConstraint): 118 """The object must be a list of objects, with a given maximum length. To 119 accept lists of any length, use maxLength=None. All member objects must 120 obey the given constraint.""" 121 122 opentypes = [("list",)] 123 name = "ListConstraint" 124 125 def __init__(self, constraint, maxLength=None, minLength=0): 126 self.constraint = IConstraint(constraint) 127 self.maxLength = maxLength 128 self.minLength = minLength 129 130 def checkObject(self, obj, inbound): 131 if not isinstance(obj, list): 132 raise Violation("not a list") 133 if self.maxLength is not None and len(obj) > self.maxLength: 134 raise Violation("list too long") 135 if len(obj) < self.minLength: 136 raise Violation("list too short") 137 for o in obj: 138 self.constraint.checkObject(o, inbound) 139