Package oort :: Module display
[hide private]
[frames] | no frames]

Source Code for Module oort.display

  1  # -*- coding: UTF-8 -*- 
  2  """ 
  3  """ 
  4  #======================================================================= 
  5  import os.path 
  6  from rdflib import RDF, RDFS, ConjunctiveGraph 
  7  from genshi.output import DocType 
  8  from genshi.template import TemplateLoader 
  9  from oort.util import templating 
 10  from oort.util.code import contract, autosuper 
 11  __metaclass__ = autosuper 
 12  #======================================================================= 
 13  # TODO: Remove all variant handling; too much arbitrariness. 
 14  # If a need for aspect-switching on request details arise, re-invent. 
 15  # (This is reasonably done within the template system). 
 16  # TODO: use Buffet-style templating instead(?) 
 17   
 18   
19 -class DisplayBase:
20 21 contentType = None 22
23 - def __init__(self, cfg=None):
24 pass
25 26 @contract.template_method
27 - def get_result_generator(self, graph, appUtil, variantArg):
28 raise NotImplementedError
29 30
31 -class Display(DisplayBase):
32 33 # TODO: for outputEncoding, see Aspect problems below. 34 # May need to redesign to set template-speficic stuff instead. 35 # These options should be available as data too (e.g. for template 36 # output and/or encoding specific stuff..). 37 outputMethod = None 38 outputEncoding = None 39 40 templateBase = "" 41 globalQueries = None 42 aspects = [] 43
44 - def __init__(self, cfg=None):
45 self.typeAspects = {} 46 self.variantAspects = {} 47 cfg = cfg or {} 48 cfg['templateBase'] = self.templateBase 49 for aspect in self.aspects: 50 aspect.post_init_setup(self.globalQueries, cfg=cfg) 51 if aspect.forVariant: 52 variant = self.variantAspects.setdefault(aspect.forVariant, {}) 53 typeDict = variant 54 else: 55 typeDict = self.typeAspects 56 typeDict[aspect.forType] = aspect
57 58 @contract.default_method
59 - def parse_variant(self, variantStr):
60 return variantStr
61
62 - def get_result_generator(self, graph, appUtil, variantArg):
63 variant = self.parse_variant(variantArg) 64 data = self.get_extra_data(appUtil, variant) or {} 65 data['app'] = appUtil 66 resource, lang = appUtil.current.resource, appUtil.current.lang 67 aspect = self.get_aspect(graph, resource, variant) 68 generate = aspect.make_result_generator(graph, resource, lang, data) 69 return generate(self.outputMethod, encoding=self.outputEncoding)
70
71 - def get_aspect(self, graph, resource, variant):
72 rdfType = RDFS.Resource 73 for _type in graph.objects(resource, RDF.type): 74 rdfType = _type; break 75 typeDict = self.typeAspects 76 if variant: 77 typeDict = self.variantAspects.get(variant) or self.typeAspects 78 return typeDict.get(rdfType) or typeDict[RDFS.Resource]
79 80 @contract.default_method
81 - def get_extra_data(self, appUtil, variant):
82 "Use for further template preparation based on current request." 83 return None
84 85
86 -class KeywordVariantParser:
87 pass # TODO
88 89
90 -class SubTypeAwareDisplay(Display):
91
92 - def __init__(self, cfg=None):
93 self.__super.__init__(cfg) 94 self._matchGraph = None
95
96 - def get_aspect(self, graph, resource, variant):
97 if not self._matchGraph: 98 self._matchGraph = self.create_match_graph(graph) 99 return self.__super.get_aspect(self._matchGraph, resource, variant)
100
101 - def create_match_graph(self, graph):
102 """For optimization, creating a sparse graph of "best matching" 103 for all resources and querying only that at runtime. """ 104 typeAspects = self.typeAspects 105 def list_with_sub_types(rdfType): 106 yield rdfType 107 for subType in graph.subjects(RDFS.subClassOf, rdfType): 108 if subType not in typeAspects: 109 for subSub in list_with_sub_types(subType): 110 yield subSub
111 112 sparseGraph = ConjunctiveGraph() 113 for handledType in typeAspects: 114 for anySub in list_with_sub_types(handledType): 115 for resource in graph.subjects(RDF.type, anySub): 116 sparseGraph.add((resource, RDF.type, handledType)) 117 118 return sparseGraph
119 120 # TODO: Implement? Not really useful if create_match_graph is used; 121 # however, this one is *much* better if resources are volatile.. 122 #def get_aspect(resource): 123 # for rType in graph.objects(resource, RDF.type): 124 # if rType in _mostSpecificHandled: 125 # return typeAspects.get(rType) 126 # supType = _handledSupers.get(rType) 127 # if supType: 128 # return typeAspects.get(supType) 129 # return typeDict[RDFS.Resource] 130 131
132 -class AspectBase:
133
134 - def __init__(self, forType, queries=None, forVariant=None):
135 self.forType = forType 136 self.queries = queries or {} 137 self.forVariant = forVariant
138 139 @contract.state_change
140 - def post_init_setup(self, globalQueries, cfg=None):
141 if globalQueries: 142 queries = globalQueries.copy() 143 queries.update(self.queries) 144 self.queries = queries 145 self.post_init_configure(cfg or {})
146 147 @contract.template_method
148 - def post_init_configure(self, cfg):
149 pass
150 151 @contract.helper
152 - def generate_query_items(self, graph, resource, lang):
153 """Generates key, value pairs which constitute named, run queries.""" 154 if self.queries: 155 for name, query in self.queries.items(): 156 yield name, query(graph, lang, resource)
157 158 # TODO: redesign to set (default) encoding and output(method) via cfg in Aspect#post_init_setup? 159 @contract.template_method
160 - def make_result_generator(self, graph, resource, lang, data=None):
161 """Returns a generator that takes the arguments 'encoding' and 162 'output'.""" 163 raise NotImplementedError
164
165 - def using(self, **queries):
166 self.queries = queries 167 return self
168
169 -class TemplateAspectBase(AspectBase):
170 - def __init__(self, forType, templateName, queries=None, forVariant=None):
171 self.__super.__init__(forType, queries, forVariant) 172 self.templateName = templateName 173 self.templateBase = None
174
175 - def post_init_configure(self, cfg):
176 self.templateBase = cfg.get('templateBase')
177
178 -class Aspect(TemplateAspectBase):
179 180 RELOAD_TEMPLATES = "reload_templates" 181 182 # TODO: use coming genshi.template.plugin.MarkupTemplateEnginePlugin.doctypes 183 doctypes = dict( 184 (key.lower().replace('_', '-'), item) 185 for key, item in DocType.__dict__.items() 186 if key.isupper() and isinstance(item, tuple) 187 ) 188
189 - def post_init_configure(self, cfg):
190 self.__super.post_init_configure(cfg) 191 autoReload = str(cfg.get(self.RELOAD_TEMPLATES, False)) == str(True) 192 self.loader = TemplateLoader(['.'], auto_reload=autoReload)
193
194 - def load_template(self):
195 return self.loader.load(os.path.join(self.templateBase, self.templateName))
196
197 - def make_result_generator(self, graph, resource, lang, data=None):
198 tplt = self.load_template() 199 allData = dict( self.generate_query_items(graph, resource, lang) ) 200 if data: allData.update(data) 201 # TODO: although genshi has a serialize (to stream) too, it doesn't 202 # take encoding. Find the simplest way to get around that (without too 203 # much duplicatiom of render innards..).. Or just remove this todo? 204 def generator(format, encoding): 205 doctype = self.doctypes.get(format) 206 format = format.split('-')[0] 207 yield tplt.generate(**allData).render( 208 format, doctype=doctype, encoding=encoding)
209 return generator
210 211
212 -class TpltAspect(TemplateAspectBase):
213 214 engineManager = templating.TemplateEngineManager(None, {}) 215 216 defaultEngine = "genshi" 217
218 - def post_init_configure(self, cfg):
219 self.__super.post_init_configure(cfg) 220 self.engine, self.path = self.engineManager.get_engine_and_path( 221 self.templateBase+'.'+self.templateName, self.defaultEngine) 222 template = self.engine.load_template(self.path) 223 self.template = template or None
224
225 - def make_result_generator(self, graph, resource, lang, data=None):
226 allData = dict(self.generate_query_items(graph, resource, lang)) 227 if data: 228 allData.update(data) 229 engine = self.engine 230 template = self.template or self.path 231 # FIXME: needs to use encoding, but alas, the plugin format precludes it... 232 def generator(format, encoding): 233 result = engine.render(allData, format=format, template=template) 234 yield result
235 return generator
236 237
238 -class RdfAspect(AspectBase):
239
240 - def __init__(self, forType, queries=None):
241 self.__super.__init__(forType, queries)
242
243 - def make_result_generator(self, graph, resource, lang, data=None):
244 qData = dict( self.generate_query_items(graph, resource, lang) ) 245 def generator(format, encoding): 246 lgraph = ConjunctiveGraph() 247 for key, result in qData.items(): 248 lgraph += result.to_graph() 249 yield lgraph.serialize(format='pretty-xml')
250 return generator
251
252 -class JsonAspect(AspectBase):
253
254 - def __init__(self, forType, queries=None, subjectKey=False):
255 self.__super.__init__(forType, queries) 256 self.subjectKey = subjectKey
257
258 - def make_result_generator(self, graph, resource, lang, data=None):
259 qData = dict( self.generate_query_items(graph, resource, lang) ) 260 def generator(format, encoding): 261 # TODO: use encoding! 262 yield '{\n' 263 for i, (key, result) in enumerate(qData.items()): 264 if i != 0: yield "," 265 yield "'%s':" % key 266 # TODO: a very crude way of dealing with long text.. 267 yield result.to_json(self.subjectKey).replace('}', '}\n') 268 yield '}\n'
269 return generator
270