1 # -*- test-case-name: twisted.test.test_roots -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Twisted Python Roots: an abstract hierarchy representation for Twisted.
8 Maintainer: Glyph Lefkowitz
13 from twisted.python import reflect
15 class NotSupportedError(NotImplementedError):
17 An exception meaning that the tree-manipulation operation
18 you're attempting to perform is not supported.
23 """I am an abstract representation of a request for an entity.
25 I also function as the response. The request is responded to by calling
26 self.write(data) until there is no data left and then calling
29 # This attribute should be set to the string name of the protocol being
30 # responded to (e.g. HTTP or FTP)
32 def write(self, data):
33 """Add some data to the response to this request.
35 raise NotImplementedError("%s.write" % reflect.qual(self.__class__))
38 """The response to this request is finished; flush all data to the network stream.
40 raise NotImplementedError("%s.finish" % reflect.qual(self.__class__))
44 """I am a terminal object in a hierarchy, with no children.
46 I represent a null interface; certain non-instance objects (strings and
47 integers, notably) are Entities.
49 Methods on this class are suggested to be implemented, but are not
50 required, and will be emulated on a per-protocol basis for types which do
53 def render(self, request):
55 I produce a stream of bytes for the request, by calling request.write()
58 raise NotImplementedError("%s.render" % reflect.qual(self.__class__))
62 """I represent a static collection of entities.
64 I contain methods designed to represent collections that can be dynamically
68 def __init__(self, entities=None):
71 if entities is not None:
72 self.entities = entities
76 def getStaticEntity(self, name):
77 """Get an entity that was added to me using putEntity.
79 This method will return 'None' if it fails.
81 return self.entities.get(name)
83 def getDynamicEntity(self, name, request):
84 """Subclass this to generate an entity on demand.
86 This method should return 'None' if it fails.
89 def getEntity(self, name, request):
90 """Retrieve an entity from me.
92 I will first attempt to retrieve an entity statically; static entities
93 will obscure dynamic ones. If that fails, I will retrieve the entity
96 If I cannot retrieve an entity, I will return 'None'.
98 ent = self.getStaticEntity(name)
101 ent = self.getDynamicEntity(name, request)
106 def putEntity(self, name, entity):
107 """Store a static reference on 'name' for 'entity'.
109 Raises a KeyError if the operation fails.
111 self.entities[name] = entity
113 def delEntity(self, name):
114 """Remove a static reference for 'name'.
116 Raises a KeyError if the operation fails.
118 del self.entities[name]
120 def storeEntity(self, name, request):
121 """Store an entity for 'name', based on the content of 'request'.
123 raise NotSupportedError("%s.storeEntity" % reflect.qual(self.__class__))
125 def removeEntity(self, name, request):
126 """Remove an entity for 'name', based on the content of 'request'.
128 raise NotSupportedError("%s.removeEntity" % reflect.qual(self.__class__))
130 def listStaticEntities(self):
131 """Retrieve a list of all name, entity pairs that I store references to.
135 return self.entities.items()
137 def listDynamicEntities(self, request):
138 """A list of all name, entity that I can generate on demand.
140 See getDynamicEntity.
144 def listEntities(self, request):
145 """Retrieve a list of all name, entity pairs I contain.
149 return self.listStaticEntities() + self.listDynamicEntities(request)
151 def listStaticNames(self):
152 """Retrieve a list of the names of entities that I store references to.
156 return self.entities.keys()
159 def listDynamicNames(self):
160 """Retrieve a list of the names of entities that I store references to.
162 See getDynamicEntity.
167 def listNames(self, request):
168 """Retrieve a list of all names for entities that I contain.
172 return self.listStaticNames()
175 class ConstraintViolation(Exception):
176 """An exception raised when a constraint is violated.
180 class Constrained(Collection):
181 """A collection that has constraints on its names and/or entities."""
183 def nameConstraint(self, name):
184 """A method that determines whether an entity may be added to me with a given name.
186 If the constraint is satisfied, return 1; if the constraint is not
187 satisfied, either return 0 or raise a descriptive ConstraintViolation.
191 def entityConstraint(self, entity):
192 """A method that determines whether an entity may be added to me.
194 If the constraint is satisfied, return 1; if the constraint is not
195 satisfied, either return 0 or raise a descriptive ConstraintViolation.
199 def reallyPutEntity(self, name, entity):
200 Collection.putEntity(self, name, entity)
202 def putEntity(self, name, entity):
203 """Store an entity if it meets both constraints.
205 Otherwise raise a ConstraintViolation.
207 if self.nameConstraint(name):
208 if self.entityConstraint(entity):
209 self.reallyPutEntity(name, entity)
211 raise ConstraintViolation("Entity constraint violated.")
213 raise ConstraintViolation("Name constraint violated.")
216 class Locked(Constrained):
217 """A collection that can be locked from adding entities."""
224 def entityConstraint(self, entity):
225 return not self.locked
228 class Homogenous(Constrained):
229 """A homogenous collection of entities.
231 I will only contain entities that are an instance of the class or type
232 specified by my 'entityType' attribute.
235 entityType = types.InstanceType
237 def entityConstraint(self, entity):
238 if isinstance(entity, self.entityType):
241 raise ConstraintViolation("%s of incorrect type (%s)" %
242 (entity, self.entityType))
244 def getNameType(self):
247 def getEntityType(self):
248 return self.entityType.__name__