root/trunk/RBFoundation/RBFoundation/XMLClassBuilder.py

Revision 644, 11.9 kB (checked in by sholloway, 5 years ago)

Made skin:trylist only catch element factory and import errors

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 #~ Imports
24 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
26 from __future__ import generators
27 import XMLBuilder
28 import keyword
29
30 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31 #~ Definitions
32 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33
34 ElementFactoryError = XMLBuilder.ElementFactoryError
35
36 class ElementFactory(object):
37     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38     #~ Definitions
39     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40
41     class Static(object):
42         def __init__(self, result):
43             self.result = result
44         def __call__(self, *args, **kw):
45             return self.result
46
47     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48
49     class Raise(object):
50         def __init__(self, exception=ElementFactoryError, message=''):
51             self.exception = exception
52             self.message = message
53
54         def __call__(self, *args, **kw):
55             raise self.exception, self.message
56
57     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58
59     class InheritFromNextFactory(Exception):
60         def __call__(self, *args, **kw):
61             raise self.__class__, (args, kw)
62
63     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64
65     class BaseImport(object):
66         retryimport = True
67
68         def __init__(self, PyPathRoot=''):
69             self.PyPathRoot = PyPathRoot
70             self._CachedElementFactories = {}
71
72         def IsEnabled(self):
73             try:
74                 return self._enabled
75             except AttributeError:
76                 try:
77                     __import__(self.PyPathRoot, globals(), {})
78                 except ImportError:
79                     self._enabled = False
80                 else:
81                     self._enabled = True
82                 return self._enabled
83
84         def _DoImport(self, PyPath, Name):
85             if not self.IsEnabled():
86                 raise ImportError, 'Import element finder %r is disabled because root %r cannot be imported' % (self, self.PyPathRoot)
87
88             if self.PyPathRoot and PyPath:
89                 PyPath = '.'.join((self.PyPathRoot, PyPath))
90             elif not PyPath:
91                 PyPath = self.PyPathRoot
92             module = __import__(PyPath, globals(), {}, Name)
93
94             try:
95                 return getattr(module, Name)
96             except AttributeError:
97                 if self.retryimport:
98                     module = reload(module)
99                     try: return getattr(module, Name)
100                     except AttributeError: pass
101                 raise ElementFactoryError, 'Could not find "%s" in module %r' % (Name, module)
102
103         def __call__(self, owner, parent, node, attributes, namespacemap):
104             result = self._CachedElementFactories.get(node, None)
105             if not result:
106                 ns, name = node
107                 if keyword.iskeyword(name): name = name + '_'
108                 result = self._DoImport('', name)
109
110                 ## The following works for another scheme
111                 ##result = self.DoImport(*node)
112
113                 self._CachedElementFactories[node] = result
114             return result
115
116     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117
118     class StaticImport(BaseImport):
119         def __init__(self, PyPath, Name, *args, **kw):
120             ElementFactory.BaseImport.__init__(self, PyPath)
121             self.name = Name
122             self.result = None
123
124         def __call__(self, *args, **kw):
125             if not self.result:
126                 self.result = self._DoImport('', self.name)
127             return self.result
128            
129     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130
131     class NodeImport(BaseImport):
132         def __call__(self, owner, parent, node, attributes, namespacemap):
133             result = self._CachedElementFactories.get(node, None)
134             if not result:
135                 ns, name = node
136                 if keyword.iskeyword(name): name = name + '_'
137                 result = self._DoImport(name, name)
138
139                 ## The following works for another scheme
140                 ##result = self.DoImport(*node)
141
142                 self._CachedElementFactories[node] = result
143             return result
144            
145     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146
147     class NamespaceImport(BaseImport):
148         def __call__(self, owner, parent, node, attributes, namespacemap):
149             result = self._CachedElementFactories.get(node, None)
150             if not result:
151                 ns, name = node
152                 if keyword.iskeyword(name): name = name + '_'
153                 result = self._DoImport('%s.%s' % (ns, name), name)
154
155                 ## The following works for another scheme
156                 ##result = self.DoImport(*node)
157
158                 self._CachedElementFactories[node] = result
159             return result
160
161     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162
163     class TryList(object):
164         def __init__(self, trylist, ignoreExceptions=[ElementFactoryError, ImportError]):#, AttributeError, KeyError]):
165             self._trylist = trylist
166             self._ignoreExceptions = tuple(ignoreExceptions)
167
168         def __call__(self, owner, parent, node, attributes, namespacemap):
169             for each in self._trylist:
170                 try:
171                     return each(owner, parent, node, attributes, namespacemap)
172                 except self._ignoreExceptions:
173                     pass
174
175             raise ElementFactoryError('No suitable element factory for node %r' % (node,))
176
177     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
178
179     class CachedTryList(TryList):
180         def __init__(self, *args, **kw):
181             ElementFactory.TryList.__init__(self, *args, **kw)
182             self._CachedElementFactories = {}
183
184         def __call__(self, owner, parent, node, attributes, namespacemap):
185             result = self._CachedElementFactories.get(node, None)
186             if result:
187                 return result
188             for each in self._trylist:
189                 try:
190                     result = each(owner, parent, node, attributes, namespacemap)
191                     self._CachedElementFactories[node] = result
192                     return result
193                 except self._ignoreExceptions, e:
194                     pass
195
196             raise ElementFactoryError('No suitable element factory for node %r' % (node,))
197
198 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
199
200 class ElementFactorySet(dict):
201     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
202     #~ Constants / Variables / Etc.
203     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
204
205     NextFactorySet = None
206     _PushCount = 0
207
208     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209     #~ Public Methods
210     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211
212     def PushFactorySet(self, FactorySet):
213         if FactorySet is not self:
214             self.NextFactorySet = FactorySet
215             self._PushCount = 1
216         else:
217             self._PushCount += 1
218         return self
219
220     def PopFactorySet(self):
221         self._PushCount -= 1
222         if self._PushCount <= 0:
223             result = self.NextFactorySet
224             self.NextFactorySet = None
225             return result
226         else:
227             return self
228
229     def copy(self):
230         return self.__class__(dict.copy(self))
231
232     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
233     #~ Protected Methods
234     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
235
236     def _GetElementFactoryIndices(self, node):
237         idx = node
238         while idx:
239             yield idx
240             idx = idx[:-1]
241         yield None
242
243     def _GetElementFactory(self, owner, parent, node, attributes, namespacemap):
244         try:
245             for idx in self._GetElementFactoryIndices(node):
246                 FactoryFactory = self.get(idx)
247                 if FactoryFactory is not None:
248                     result = FactoryFactory(owner, parent, node, attributes, namespacemap)
249                     if result:
250                         return result
251         except ElementFactory.InheritFromNextFactory, (args, kw):
252             if self.NextFactorySet:
253                 return self.NextFactorySet._GetElementFactory(*args, **kw)
254
255         raise ElementFactoryError("Could not find a class to build for node %r" % (node,))
256
257 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258
259 class XMLClassBuilderBaseMixin(object):
260     """Mixin that forms the python import indirectly from a dictionary lookup of the namespace,
261     and then combines the result with the node name."""
262
263     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
264     #~ Constants / Variables / Etc.
265     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266
267     ElementFactories = ElementFactorySet()
268
269     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
270     #~ Public Methods
271     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
272
273     def AddElementFactory(self, idx, Factory):
274         self.ElementFactorySet[idx] = Factory
275
276     def RemoveElementFactory(self, idx):
277         try:
278             del self.ElementFactorySet[idx]
279             return True
280         except KeyError:
281             return False
282
283     def AddElementFactories(self, Factories):
284         """Registeres a new mapping between xmlns and python import strings."""
285         # Did they send us a real change?
286         if Factories:
287             # And now update with what they gave us
288             self.ElementFactorySet.update(Factories)
289
290     def RemoveElementFactories(self, Factories):
291         # Did they send us a real change?
292         return map(self.RemoveElementFactory, Frctories or ())
293
294     def PushElementFactorySet(self, FactorySet):
295         result = self.ElementFactories
296         self.ElementFactories = FactorySet.PushFactorySet(result)
297         return result
298
299     def PopElementFactorySet(self):
300         result = self.ElementFactories
301         self.ElementFactories = result.PopFactorySet()
302         return result
303  
304     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305     #~ Protected Methods
306     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307
308     def _GetModifableElementFactoryDict(self):
309         # Are we working on the class dictionary?
310         if self.ElementFactories is self.__class__.ElementFactories:
311             # If so, lets get an instance copy
312             self.ElementFactories = self.ElementFactories.copy()
313         return self.ElementFactories
314     ElementFactorySet = property(_GetModifableElementFactoryDict)
315
316 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
317
318 class XMLClassBuilderMixin(XMLClassBuilderBaseMixin, XMLBuilder.XMLBuilderMixin):
319     def _GetElementFactory(self, owner, parent, node, attributes, namespacemap):
320         if self._elements:
321             result = self._elements[-1]._xmlChildFactory(owner, parent, node, attributes, namespacemap)
322             if result: return result
323         return self.ElementFactorySet._GetElementFactory(owner, parent, node, attributes, namespacemap)
324
325 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
326
327 class XMLClassBuilderObjectMixin(XMLClassBuilderBaseMixin):
328     def _xmlChildFactory(self, owner, parent, node, attributes, namespacemap):
329         return self.ElementFactorySet._GetElementFactory(owner, parent, node, attributes, namespacemap)
330
331 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
332
333 class XMLClassBuilder(XMLClassBuilderMixin, XMLBuilder.XMLExpatBuilderMixin):
334     pass
335
Note: See TracBrowser for help on using the browser.