Package gavo :: Package web :: Module serviceresults
[frames] | no frames]

Source Code for Module gavo.web.serviceresults

  1  """ 
  2  "Output drivers" for various formats, for the use of form-like renderers. 
  3   
  4  TODO: Tar and the output format widget should go somewhere else; the 
  5  rest should be done by RESPONSEFORMAT and formats.  Then this module 
  6  should die. 
  7  """ 
  8   
  9  #c Copyright 2008-2019, the GAVO project 
 10  #c 
 11  #c This program is free software, covered by the GNU GPL.  See the 
 12  #c COPYING file in the source distribution. 
 13   
 14   
 15  import os 
 16   
 17  from nevow import inevow 
 18  from nevow import static 
 19  from nevow import tags as T 
 20  from twisted.internet import threads 
 21   
 22  from gavo import base 
 23  from gavo import formats 
 24  from gavo import utils 
 25  from gavo.formats import csvtable #noflake: format registration 
 26  from gavo.formats import jsontable #noflake: format registration 
 27  from gavo.formats import fitstable #noflake: format registration 
 28  from gavo.formats import texttable #noflake: format registration 
 29  from gavo.formats import geojson #noflake: format registration 
 30  from gavo.imp.formal import types as formaltypes 
 31  from gavo.imp.formal.util import render_cssid 
 32  from gavo.protocols import products 
 33  from gavo.svcs import customwidgets 
 34  from gavo.svcs import streaming 
 35  from gavo.web import producttar 
 36   
 37  __docformat__ = "restructuredtext en" 
38 39 40 -class ServiceResult(object):
41 """A base class for objects producing formatted output. 42 43 ServiceResults are constructed with a context and the service. 44 On renderHTTP, they will spawn a thread to compute the service 45 result (usually, an rsc.Data instance) and hand that over 46 to _formatOutput. 47 48 All methods on these objects are class methods -- they are never 49 instanciated. 50 51 Deriving classes can override 52 53 - _formatOutput(result, ctx) -- receives the service result and 54 has to format it. 55 - canWrite(colSeq) -- a that returns true when a seq of columns 56 can be serialized by this service result. 57 - code -- an identifier used in HTTP query strings to select the format 58 - label (optional) -- a string that is used in HTML forms to select 59 the format (defaults to label). 60 - compute -- if False, at least the form renderer will not run 61 the service (this is when you just return a container). 62 """ 63 64 compute = True 65 code = None 66 label = None 67 68 @classmethod
69 - def _formatOutput(cls, res, ctx):
70 return ""
71 72 @classmethod
73 - def getLabel(cls):
74 if cls.label is not None: 75 return cls.label 76 return cls.code
77 78 @classmethod
79 - def canWrite(cls, colSeq):
80 return True
81
82 83 -class VOTableResult(ServiceResult):
84 """A ResultFormatter for VOTables. 85 86 The VOTables come as attachments, i.e., if all goes well the form 87 will just stand as it is. 88 """ 89 code = "VOTable" 90 91 @classmethod
92 - def _formatOutput(cls, data, ctx):
93 request = inevow.IRequest(ctx) 94 if base.getMetaText(data.original.getPrimaryTable(), "_queryStatus" 95 )=="OVERFLOW": 96 fName = "truncated_votable.xml" 97 else: 98 fName = "votable.xml" 99 request.setHeader("content-type", base.votableType) 100 request.setHeader('content-disposition', 101 'attachment; filename=%s'%fName) 102 return streaming.streamVOTable(request, data)
103
104 105 -class FITSTableResult(ServiceResult):
106 """returns data as a FITS binary table. 107 """ 108 code = "FITS" 109 label = "FITS table" 110 111 @classmethod
112 - def getTargetName(cls, data):
113 if base.getMetaText(data.original.getPrimaryTable(), "_queryStatus" 114 )=="OVERFLOW": 115 return "truncated_data.fits", "application/x-fits" 116 else: 117 return "data.fits", "application/x-fits"
118 119 @classmethod
120 - def _formatOutput(cls, data, ctx):
121 return threads.deferToThread(fitstable.makeFITSTableFile, data.original 122 ).addCallback(cls._serveFile, data, ctx)
123 124 @classmethod
125 - def _serveFile(cls, filePath, data, ctx):
126 request = inevow.IRequest(ctx) 127 name, mime = cls.getTargetName(data) 128 request.setHeader("content-type", mime) 129 request.setHeader('content-disposition', 130 'attachment; filename=%s'%name) 131 static.FileTransfer(open(filePath), os.path.getsize(filePath), 132 request) 133 os.unlink(filePath) 134 return request.deferred
135
136 137 -class TSVResponse(ServiceResult):
138 code = "TSV" 139 label = "Text (with Tabs)" 140 141 @classmethod
142 - def _formatOutput(cls, data, ctx):
143 request = inevow.IRequest(ctx) 144 content = texttable.getAsText(data.original) 145 request.setHeader('content-disposition', 146 'attachment; filename=table.tsv') 147 request.setHeader("content-type", "text/tab-separated-values") 148 request.setHeader("content-length", len(content)) 149 request.write(content) 150 return ""
151
152 153 -class TextResponse(ServiceResult):
154 code = "txt" 155 label = "Text (fixed columns)" 156 157 @classmethod
158 - def _formatOutput(cls, data, ctx):
159 request = inevow.IRequest(ctx) 160 request.setHeader("content-type", "text/plain") 161 162 def produceData(destFile): 163 formats.formatData("txt", data.original, 164 request, acquireSamples=False)
165 166 return streaming.streamOut(produceData, request)
167
168 169 170 -class CSVResponse(ServiceResult):
171 code = "CSV" 172 label = "CSV" 173 174 @classmethod
175 - def _formatOutput(cls, data, ctx):
176 request = inevow.IRequest(ctx) 177 request.setHeader('content-disposition', 178 'attachment; filename=table.csv') 179 request.setHeader("content-type", "text/csv;header=present") 180 181 def produceData(destFile): 182 formats.formatData("csv_header", data.original, 183 request, acquireSamples=False)
184 185 return streaming.streamOut(produceData, request)
186
187 188 -class JsonResponse(ServiceResult):
189 code = "JSON" 190 label = "JSON" 191 192 @classmethod
193 - def _formatOutput(cls, data, ctx):
194 request = inevow.IRequest(ctx) 195 request.setHeader('content-disposition', 196 'attachment; filename=table.json') 197 request.setHeader("content-type", "application/json") 198 199 def produceData(destFile): 200 formats.formatData("json", data.original, 201 request, acquireSamples=False)
202 203 return streaming.streamOut(produceData, request)
204
205 206 -class TarResponse(ServiceResult):
207 """delivers a tar of products requested. 208 """ 209 code = "tar" 210 211 @classmethod
212 - def _formatOutput(cls, data, ctx):
213 queryMeta = data.queryMeta 214 request = inevow.IRequest(ctx) 215 return producttar.getTarMaker().deliverProductTar( 216 data, request, queryMeta)
217 218 @classmethod
219 - def canWrite(cls, colSeq):
220 if products.getProductColumns(colSeq): 221 return True 222 return False
223 224 225 ################# Helpers 226 227 228 _getFormat = utils.buildClassResolver(ServiceResult, 229 globals().values(), key=lambda obj: obj.code)
230 231 232 -def getFormat(formatName):
233 try: 234 return _getFormat(formatName) 235 except KeyError: 236 raise base.ValidationError("Unknown format '%s'."%formatName, 237 "_OUTPUT")
238
239 240 -class OutputFormat(object):
241 """A widget that offers various output options in close cooperation 242 with gavo.js and QueryMeta. 243 244 The javascript provides options for customizing output that non-javascript 245 users will not see. Also, formal doesn't see any of these. See gavo.js 246 for details. 247 248 This widget probably only makes sense in the Form renderer and thus 249 should probably go there. 250 """
251 - def __init__(self, typeOb, service, queryMeta):
252 self.service = service 253 self.typeOb = typeOb 254 self._computeAvailableFields(queryMeta) 255 self._computeAvailableFormats(queryMeta)
256
257 - def _computeAvailableFormats(self, queryMeta):
258 """sets the availableFormats property. 259 260 This is a helper for the constructor, inspecting serviceresults' globals(). 261 """ 262 outputFields = self.service.getCurOutputFields( 263 queryMeta, 264 raiseOnUnknown=False) 265 self.availableFormats = [ 266 (code, format.getLabel()) 267 for code, format in _getFormat.registry.iteritems() 268 if format.canWrite(outputFields)]
269
270 - def _computeAvailableFields(self, queryMeta):
271 """computes the fields a Core provides but are not output by 272 the service by default. 273 274 This of course only works if the core defines its output table. 275 Otherwise, availableFields is an empty list. 276 """ 277 self.availableFields = [] 278 core = self.service.core 279 if (not core.outputTable 280 or self.service.getProperty("noAdditionals", False)): 281 return 282 coreNames = set(f.name for f in core.outputTable) 283 defaultNames = set([f.name 284 for f in self.service.getHTMLOutputFields( 285 queryMeta, 286 ignoreAdditionals=True, 287 raiseOnUnknown=False)]) 288 289 for key in coreNames-defaultNames: 290 try: 291 self.availableFields.append((core.outputTable.getColumnByName(key), 292 key in queryMeta["additionalFields"])) 293 except KeyError: # Core returns fields not in its table, 294 # probably computes them 295 pass
296
297 - def _makeAdditionalSelector(self):
298 """returns an ul element containing form material for additional output 299 columns. 300 """ 301 checkLiterals = {True: "checked", False: None} 302 fields = [] 303 for column, checked in sorted( 304 self.availableFields, key=lambda p:p[0].name): 305 fields.append(T.tr[ 306 T.td[ 307 T.input(type="checkbox", name="_ADDITEM", value=column.key, 308 style="width:auto", 309 checked=checkLiterals[checked])], 310 T.td(style="vertical-align:middle")[ 311 " %s -- %s"%(column.name, column.description)]]) 312 return T.table(id="addSelection")[fields]
313
314 - def render(self, ctx, key, args, errors):
315 res = T.div(id=render_cssid("_OUTPUT"), style="position:relative")[ 316 customwidgets.SelectChoice(formaltypes.String(), 317 options=self.availableFormats, 318 noneOption=("HTML", "HTML")).render(ctx, "_FORMAT", args, errors)( 319 onchange="output_broadcast(this.value)")] 320 321 if self.availableFields: 322 res[ 323 T.div(title="Additional output column selector", 324 id=render_cssid("_ADDITEMS"), 325 style="visibility:hidden;position:absolute;")[ 326 self._makeAdditionalSelector()]] 327 return res
328 329 renderImmutable = render # This is a lost case 330
331 - def processInput(self, ctx, key, args):
332 return args.get("_FORMAT", ["HTML"])[0]
333