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

Source Code for Module gavo.web.taprender

  1  """ 
  2  A renderer for TAP, both sync and async. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import os 
 12  from cStringIO import StringIO 
 13   
 14  from nevow import inevow 
 15  from nevow import rend 
 16  from twisted.internet import threads 
 17   
 18  from gavo import base 
 19  from gavo import svcs 
 20  from gavo import utils 
 21  from gavo.protocols import tap 
 22  from gavo.protocols import taprunner 
 23  from gavo.protocols import uws 
 24  from gavo.protocols import uwsactions 
 25  from gavo.svcs import streaming 
 26  from gavo.web import asyncrender 
 27  from gavo.web import common 
 28  from gavo.web import grend 
 29  from gavo.web import vosi 
30 31 32 @utils.memoized 33 -def getTAPVersion():
34 return base.caches.getRD(tap.RD_ID).getProperty("TAP_VERSION")
35
36 37 -class TAPQueryResource(rend.Page):
38 """the resource executing sync TAP queries. 39 40 While not really going through UWS, this does create a UWS job and 41 tears it down later so we can re-use all the parsing and parameter 42 interpretation of the TAP job. 43 """
44 - def __init__(self, service, ctx):
45 self.service = service 46 rend.Page.__init__(self)
47
48 - def _doRender(self, ctx):
49 jobId = tap.WORKER_SYSTEM.getNewIdFromRequest( 50 inevow.IRequest(ctx), self.service) 51 52 try: 53 taprunner.runSyncTAPJob(jobId) 54 55 job = tap.WORKER_SYSTEM.getJob(jobId) 56 if job.phase==uws.COMPLETED: 57 # This is TAP, so there's exactly one result 58 res = job.getResults()[0] 59 name, type = res["resultName"], res["resultType"] 60 # hold on to the result fd so its inode is not lost when we delete 61 # the job. 62 f = open(os.path.join(job.getWD(), name)) 63 return (f, type) 64 elif job.phase==uws.ERROR: 65 exc = job.error 66 raise base.Error(exc["msg"], hint=exc["hint"]) 67 elif job.phase==uws.ABORTED: 68 raise uws.UWSError("Job was manually aborted. For synchronous" 69 " jobs, this probably means the operators killed it.", 70 jobId) 71 else: 72 raise uws.UWSError("Internal error. Invalid UWS phase.", jobId) 73 finally: 74 tap.WORKER_SYSTEM.destroy(jobId)
75
76 - def renderHTTP(self, ctx):
77 try: 78 return threads.deferToThread(self._doRender, ctx 79 ).addCallback(self._formatResult, ctx 80 ).addErrback(self._formatError) 81 except base.Error as ex: 82 base.ui.notifyExceptionMutation(None) 83 return uwsactions.ErrorResource(ex)
84
85 - def _formatError(self, failure):
86 base.ui.notifyFailure(failure) 87 return uwsactions.ErrorResource(failure.value)
88
89 - def _formatResult(self, res, ctx):
90 request = inevow.IRequest(ctx) 91 f, type = res 92 93 def writeTable(outputFile): 94 utils.cat(f, outputFile)
95 96 request.setHeader("content-type", str(type)) 97 # if request has an accumulator, we're testing. 98 if hasattr(request, "accumulator"): 99 writeTable(request) 100 return "" 101 else: 102 return streaming.streamOut(writeTable, request)
103
104 105 -def getSyncResource(ctx, service, segments):
106 if segments: 107 raise svcs.UnknownURI("No resources below sync") 108 request = common.getfirst(ctx, "request", None) 109 if request=="getCapabilities": 110 return vosi.VOSICapabilityRenderer(ctx, service) 111 else: 112 return TAPQueryResource(service, ctx)
113
114 115 -class _FakeUploadedFile(object):
116 # File uploads without filenames are args containing a string. 117 # This class lets them work as uploaded files in _saveUpload.
118 - def __init__(self, name, content):
119 self.filename = name 120 self.file = StringIO(content)
121
122 # TODO: we should probably define different renderers for sync, 123 # async. The renderer shouldn't have to dispatch 124 # like that. 125 126 -class TAPRenderer(grend.ServiceBasedPage):
127 """A renderer speaking all of TAP (including sync, async, and VOSI). 128 129 Basically, this just dispatches to the sync and async resources. 130 """ 131 name = "tap" 132 urlUse = "base" 133
134 - def renderHTTP(self, ctx):
135 # The root resource redirects to an info on TAP 136 raise svcs.WebRedirect(self.service.getURL("info", absolute=False))
137
138 - def gatherUploadFiles(self, request):
139 """creates a files attribute on request, containing all uploaded 140 files. 141 142 The upload files are removed from args, which is good since we 143 don't want to serialize those in the parameters dictionary. 144 145 This method inspects all upload parameters and converts the 146 referenced arguments to cgi-like files as necessary. Missing 147 uploads will be noticed here, and the request will be rejected. 148 149 Of course, all that hurts if someone manages to upload from REQUEST -- 150 but that's their fault then. 151 """ 152 request.files = {} 153 for uploadSpec in request.args.get("upload", []): 154 if uploadSpec: 155 for tableName, upload in tap.parseUploadString(uploadSpec): 156 if upload.startswith("param:"): 157 paramName = upload[6:] 158 if paramName not in request.args or not request.args[paramName]: 159 raise base.ReportableError("No parameter for upload" 160 " table %s"%tableName) 161 162 item = request.args.pop(paramName)[0] 163 # fix if it doesn't already look like a file 164 if getattr(item, "file", None) is None: 165 item = _FakeUploadedFile( 166 "unnamed_inline_upload_%s"%paramName, item) 167 request.files[paramName] = item
168
169 - def _doTAP10CompatCode(self, request):
170 """changes request so our TAP 1.1 code keeps working with 1.0 clients. 171 172 This is called once, at the very start of handling stuff. 173 """ 174 if "format" in request.args: 175 formatVal = request.args.pop("format") 176 if not "responseformat" in request.args: 177 request.args["responseformat"] = formatVal
178
179 - def locateChild(self, ctx, segments):
180 request = inevow.IRequest(ctx) 181 uwsactions.lowercaseProtocolArgs(request.args) 182 self._doTAP10CompatCode(request) 183 184 if not segments[-1]: # trailing slashes are forbidden here 185 if len(segments)==1: # root resource; don't redirect, it would be a loop 186 return self, () 187 raise svcs.WebRedirect( 188 self.service.getURL("tap")+"/"+"/".join(segments[:-1])) 189 190 try: 191 self.gatherUploadFiles(request) 192 if (getTAPVersion()!= 193 utils.getfirst(request.args, "version", getTAPVersion())): 194 return uwsactions.ErrorResource({ 195 "msg": "Version mismatch; this service only supports" 196 " TAP version %s."%getTAPVersion(), 197 "type": "ValueError", 198 "hint": ""}), () 199 if segments: 200 if segments[0]=='sync': 201 return getSyncResource(ctx, self.service, segments[1:]), () 202 elif segments[0]=='async': 203 return asyncrender.getAsyncResource( 204 ctx, tap.WORKER_SYSTEM, "tap", self.service, segments[1:]), () 205 elif segments[0]=='availability': 206 res = vosi.VOSIAvailabilityRenderer(ctx, self.service) 207 elif segments[0]=='capabilities': 208 res = vosi.VOSICapabilityRenderer(ctx, self.service) 209 elif segments[0]=='tables': 210 res = vosi.VOSITablesetRenderer(ctx, self.service) 211 elif segments[0]=='examples': 212 from gavo.web import examplesrender 213 res = examplesrender.Examples(ctx, self.service) 214 else: 215 raise svcs.UnknownURI("Bad TAP path %s"%"/".join(segments)) 216 return res, segments[1:] 217 except svcs.UnknownURI: 218 raise 219 except base.Error as ex: 220 # see flagError in protocols.uws for the reason for the next if 221 if not isinstance(ex, (base.ValidationError, uws.JobNotFound)): 222 base.ui.notifyError("TAP error") 223 return uwsactions.ErrorResource(ex), () 224 raise common.UnknownURI("Bad TAP path %s"%"/".join(segments))
225