roots.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. # -*- test-case-name: twisted.test.test_roots -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Twisted Python Roots: an abstract hierarchy representation for Twisted.
  6. Maintainer: Glyph Lefkowitz
  7. """
  8. from __future__ import absolute_import, division
  9. from twisted.python import reflect
  10. from twisted.python._oldstyle import _oldStyle
  11. class NotSupportedError(NotImplementedError):
  12. """
  13. An exception meaning that the tree-manipulation operation
  14. you're attempting to perform is not supported.
  15. """
  16. @_oldStyle
  17. class Request:
  18. """I am an abstract representation of a request for an entity.
  19. I also function as the response. The request is responded to by calling
  20. self.write(data) until there is no data left and then calling
  21. self.finish().
  22. """
  23. # This attribute should be set to the string name of the protocol being
  24. # responded to (e.g. HTTP or FTP)
  25. wireProtocol = None
  26. def write(self, data):
  27. """Add some data to the response to this request.
  28. """
  29. raise NotImplementedError("%s.write" % reflect.qual(self.__class__))
  30. def finish(self):
  31. """The response to this request is finished; flush all data to the network stream.
  32. """
  33. raise NotImplementedError("%s.finish" % reflect.qual(self.__class__))
  34. @_oldStyle
  35. class Entity:
  36. """I am a terminal object in a hierarchy, with no children.
  37. I represent a null interface; certain non-instance objects (strings and
  38. integers, notably) are Entities.
  39. Methods on this class are suggested to be implemented, but are not
  40. required, and will be emulated on a per-protocol basis for types which do
  41. not handle them.
  42. """
  43. def render(self, request):
  44. """
  45. I produce a stream of bytes for the request, by calling request.write()
  46. and request.finish().
  47. """
  48. raise NotImplementedError("%s.render" % reflect.qual(self.__class__))
  49. @_oldStyle
  50. class Collection:
  51. """I represent a static collection of entities.
  52. I contain methods designed to represent collections that can be dynamically
  53. created.
  54. """
  55. def __init__(self, entities=None):
  56. """Initialize me.
  57. """
  58. if entities is not None:
  59. self.entities = entities
  60. else:
  61. self.entities = {}
  62. def getStaticEntity(self, name):
  63. """Get an entity that was added to me using putEntity.
  64. This method will return 'None' if it fails.
  65. """
  66. return self.entities.get(name)
  67. def getDynamicEntity(self, name, request):
  68. """Subclass this to generate an entity on demand.
  69. This method should return 'None' if it fails.
  70. """
  71. def getEntity(self, name, request):
  72. """Retrieve an entity from me.
  73. I will first attempt to retrieve an entity statically; static entities
  74. will obscure dynamic ones. If that fails, I will retrieve the entity
  75. dynamically.
  76. If I cannot retrieve an entity, I will return 'None'.
  77. """
  78. ent = self.getStaticEntity(name)
  79. if ent is not None:
  80. return ent
  81. ent = self.getDynamicEntity(name, request)
  82. if ent is not None:
  83. return ent
  84. return None
  85. def putEntity(self, name, entity):
  86. """Store a static reference on 'name' for 'entity'.
  87. Raises a KeyError if the operation fails.
  88. """
  89. self.entities[name] = entity
  90. def delEntity(self, name):
  91. """Remove a static reference for 'name'.
  92. Raises a KeyError if the operation fails.
  93. """
  94. del self.entities[name]
  95. def storeEntity(self, name, request):
  96. """Store an entity for 'name', based on the content of 'request'.
  97. """
  98. raise NotSupportedError("%s.storeEntity" % reflect.qual(self.__class__))
  99. def removeEntity(self, name, request):
  100. """Remove an entity for 'name', based on the content of 'request'.
  101. """
  102. raise NotSupportedError("%s.removeEntity" % reflect.qual(self.__class__))
  103. def listStaticEntities(self):
  104. """Retrieve a list of all name, entity pairs that I store references to.
  105. See getStaticEntity.
  106. """
  107. return self.entities.items()
  108. def listDynamicEntities(self, request):
  109. """A list of all name, entity that I can generate on demand.
  110. See getDynamicEntity.
  111. """
  112. return []
  113. def listEntities(self, request):
  114. """Retrieve a list of all name, entity pairs I contain.
  115. See getEntity.
  116. """
  117. return self.listStaticEntities() + self.listDynamicEntities(request)
  118. def listStaticNames(self):
  119. """Retrieve a list of the names of entities that I store references to.
  120. See getStaticEntity.
  121. """
  122. return self.entities.keys()
  123. def listDynamicNames(self):
  124. """Retrieve a list of the names of entities that I store references to.
  125. See getDynamicEntity.
  126. """
  127. return []
  128. def listNames(self, request):
  129. """Retrieve a list of all names for entities that I contain.
  130. See getEntity.
  131. """
  132. return self.listStaticNames()
  133. class ConstraintViolation(Exception):
  134. """An exception raised when a constraint is violated.
  135. """
  136. class Constrained(Collection):
  137. """A collection that has constraints on its names and/or entities."""
  138. def nameConstraint(self, name):
  139. """A method that determines whether an entity may be added to me with a given name.
  140. If the constraint is satisfied, return 1; if the constraint is not
  141. satisfied, either return 0 or raise a descriptive ConstraintViolation.
  142. """
  143. return 1
  144. def entityConstraint(self, entity):
  145. """A method that determines whether an entity may be added to me.
  146. If the constraint is satisfied, return 1; if the constraint is not
  147. satisfied, either return 0 or raise a descriptive ConstraintViolation.
  148. """
  149. return 1
  150. def reallyPutEntity(self, name, entity):
  151. Collection.putEntity(self, name, entity)
  152. def putEntity(self, name, entity):
  153. """Store an entity if it meets both constraints.
  154. Otherwise raise a ConstraintViolation.
  155. """
  156. if self.nameConstraint(name):
  157. if self.entityConstraint(entity):
  158. self.reallyPutEntity(name, entity)
  159. else:
  160. raise ConstraintViolation("Entity constraint violated.")
  161. else:
  162. raise ConstraintViolation("Name constraint violated.")
  163. class Locked(Constrained):
  164. """A collection that can be locked from adding entities."""
  165. locked = 0
  166. def lock(self):
  167. self.locked = 1
  168. def entityConstraint(self, entity):
  169. return not self.locked
  170. class Homogenous(Constrained):
  171. """A homogenous collection of entities.
  172. I will only contain entities that are an instance of the class or type
  173. specified by my 'entityType' attribute.
  174. """
  175. entityType = object
  176. def entityConstraint(self, entity):
  177. if isinstance(entity, self.entityType):
  178. return 1
  179. else:
  180. raise ConstraintViolation("%s of incorrect type (%s)" %
  181. (entity, self.entityType))
  182. def getNameType(self):
  183. return "Name"
  184. def getEntityType(self):
  185. return self.entityType.__name__