Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / python / roots.py
1 # -*- test-case-name: twisted.test.test_roots -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Twisted Python Roots: an abstract hierarchy representation for Twisted.
7
8 Maintainer: Glyph Lefkowitz
9 """
10
11 # System imports
12 import types
13 from twisted.python import reflect
14
15 class NotSupportedError(NotImplementedError):
16     """
17     An exception meaning that the tree-manipulation operation
18     you're attempting to perform is not supported.
19     """
20
21
22 class Request:
23     """I am an abstract representation of a request for an entity.
24
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
27     self.finish().
28     """
29     # This attribute should be set to the string name of the protocol being
30     # responded to (e.g. HTTP or FTP)
31     wireProtocol = None
32     def write(self, data):
33         """Add some data to the response to this request.
34         """
35         raise NotImplementedError("%s.write" % reflect.qual(self.__class__))
36
37     def finish(self):
38         """The response to this request is finished; flush all data to the network stream.
39         """
40         raise NotImplementedError("%s.finish" % reflect.qual(self.__class__))
41
42
43 class Entity:
44     """I am a terminal object in a hierarchy, with no children.
45
46     I represent a null interface; certain non-instance objects (strings and
47     integers, notably) are Entities.
48
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
51     not handle them.
52     """
53     def render(self, request):
54         """
55         I produce a stream of bytes for the request, by calling request.write()
56         and request.finish().
57         """
58         raise NotImplementedError("%s.render" % reflect.qual(self.__class__))
59
60
61 class Collection:
62     """I represent a static collection of entities.
63
64     I contain methods designed to represent collections that can be dynamically
65     created.
66     """
67
68     def __init__(self, entities=None):
69         """Initialize me.
70         """
71         if entities is not None:
72             self.entities = entities
73         else:
74             self.entities = {}
75
76     def getStaticEntity(self, name):
77         """Get an entity that was added to me using putEntity.
78
79         This method will return 'None' if it fails.
80         """
81         return self.entities.get(name)
82
83     def getDynamicEntity(self, name, request):
84         """Subclass this to generate an entity on demand.
85
86         This method should return 'None' if it fails.
87         """
88
89     def getEntity(self, name, request):
90         """Retrieve an entity from me.
91
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
94         dynamically.
95
96         If I cannot retrieve an entity, I will return 'None'.
97         """
98         ent = self.getStaticEntity(name)
99         if ent is not None:
100             return ent
101         ent = self.getDynamicEntity(name, request)
102         if ent is not None:
103             return ent
104         return None
105
106     def putEntity(self, name, entity):
107         """Store a static reference on 'name' for 'entity'.
108
109         Raises a KeyError if the operation fails.
110         """
111         self.entities[name] = entity
112
113     def delEntity(self, name):
114         """Remove a static reference for 'name'.
115
116         Raises a KeyError if the operation fails.
117         """
118         del self.entities[name]
119
120     def storeEntity(self, name, request):
121         """Store an entity for 'name', based on the content of 'request'.
122         """
123         raise NotSupportedError("%s.storeEntity" % reflect.qual(self.__class__))
124
125     def removeEntity(self, name, request):
126         """Remove an entity for 'name', based on the content of 'request'.
127         """
128         raise NotSupportedError("%s.removeEntity" % reflect.qual(self.__class__))
129
130     def listStaticEntities(self):
131         """Retrieve a list of all name, entity pairs that I store references to.
132
133         See getStaticEntity.
134         """
135         return self.entities.items()
136
137     def listDynamicEntities(self, request):
138         """A list of all name, entity that I can generate on demand.
139
140         See getDynamicEntity.
141         """
142         return []
143
144     def listEntities(self, request):
145         """Retrieve a list of all name, entity pairs I contain.
146
147         See getEntity.
148         """
149         return self.listStaticEntities() + self.listDynamicEntities(request)
150
151     def listStaticNames(self):
152         """Retrieve a list of the names of entities that I store references to.
153
154         See getStaticEntity.
155         """
156         return self.entities.keys()
157
158
159     def listDynamicNames(self):
160         """Retrieve a list of the names of entities that I store references to.
161
162         See getDynamicEntity.
163         """
164         return []
165
166
167     def listNames(self, request):
168         """Retrieve a list of all names for entities that I contain.
169
170         See getEntity.
171         """
172         return self.listStaticNames()
173
174
175 class ConstraintViolation(Exception):
176     """An exception raised when a constraint is violated.
177     """
178
179
180 class Constrained(Collection):
181     """A collection that has constraints on its names and/or entities."""
182
183     def nameConstraint(self, name):
184         """A method that determines whether an entity may be added to me with a given name.
185
186         If the constraint is satisfied, return 1; if the constraint is not
187         satisfied, either return 0 or raise a descriptive ConstraintViolation.
188         """
189         return 1
190
191     def entityConstraint(self, entity):
192         """A method that determines whether an entity may be added to me.
193
194         If the constraint is satisfied, return 1; if the constraint is not
195         satisfied, either return 0 or raise a descriptive ConstraintViolation.
196         """
197         return 1
198
199     def reallyPutEntity(self, name, entity):
200         Collection.putEntity(self, name, entity)
201
202     def putEntity(self, name, entity):
203         """Store an entity if it meets both constraints.
204
205         Otherwise raise a ConstraintViolation.
206         """
207         if self.nameConstraint(name):
208             if self.entityConstraint(entity):
209                 self.reallyPutEntity(name, entity)
210             else:
211                 raise ConstraintViolation("Entity constraint violated.")
212         else:
213             raise ConstraintViolation("Name constraint violated.")
214
215
216 class Locked(Constrained):
217     """A collection that can be locked from adding entities."""
218
219     locked = 0
220
221     def lock(self):
222         self.locked = 1
223
224     def entityConstraint(self, entity):
225         return not self.locked
226
227
228 class Homogenous(Constrained):
229     """A homogenous collection of entities.
230
231     I will only contain entities that are an instance of the class or type
232     specified by my 'entityType' attribute.
233     """
234
235     entityType = types.InstanceType
236
237     def entityConstraint(self, entity):
238         if isinstance(entity, self.entityType):
239             return 1
240         else:
241             raise ConstraintViolation("%s of incorrect type (%s)" %
242                                       (entity, self.entityType))
243
244     def getNameType(self):
245         return "Name"
246
247     def getEntityType(self):
248         return self.entityType.__name__