Source code for gavo.web.taprender

"""
A renderer for TAP, both sync and async.
"""

#c Copyright 2008-2020, the GAVO project
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


import os

from twisted.internet import threads
from twisted.web import server

from gavo import base
from gavo import svcs
from gavo import utils
from gavo.formal import nevowc
from gavo.protocols import tap
from gavo.protocols import taprunner
from gavo.protocols import uws
from gavo.protocols import uwsactions
from gavo.svcs import streaming
from gavo.web import asyncrender
from gavo.web import common
from gavo.web import grend
from gavo.web import vosi


[docs]@utils.memoized def getTAPVersion(): return base.getMetaText(base.caches.getRD(tap.RD_ID), "tapversion")
[docs]class TAPQueryResource(nevowc.TemplatedPage): """the resource executing sync TAP queries. While not really going through UWS, this does create a UWS job and tears it down later so we can re-use all the parsing and parameter interpretation of the TAP job. """ def __init__(self, service): self.service = service nevowc.TemplatedPage.__init__(self) def _doRender(self, request): jobId = tap.WORKER_SYSTEM.getNewIdFromRequest( request, self.service) try: taprunner.runSyncTAPJob(jobId, svcs.QueryMeta.fromRequest(request)) job = tap.WORKER_SYSTEM.getJob(jobId) if job.phase==uws.COMPLETED: # This is TAP, so there's exactly one result res = job.getResults()[0] name, type = res["resultName"], res["resultType"] # hold on to the result fd so its inode is not lost when we delete # the job. f = open(os.path.join(job.getWD(), name), "rb") return (f, type) elif job.phase==uws.ERROR: exc = job.error raise base.Error(exc["msg"], hint=exc["hint"]) elif job.phase==uws.ABORTED: raise uws.UWSError("Job was manually aborted. For synchronous" " jobs, this probably means the operators killed it.", jobId) else: raise uws.UWSError("Internal error. Invalid UWS phase.", jobId) finally: tap.WORKER_SYSTEM.destroy(jobId)
[docs] def render(self, request): threads.deferToThread(self._doRender, request ).addCallback(self._formatResult, request ).addErrback(self._serveError, request) return server.NOT_DONE_YET
def _serveError(self, failure, request): if not isinstance(failure.value, base.Error): base.ui.notifyFailure(failure) uwsactions.ErrorResource(failure.value).render(request) def _formatResult(self, res, request): f, type = res def writeTable(outputFile): utils.cat(f, outputFile) request.setHeader("content-type", str(type)) # if request has an accumulator, we're testing; this might be sync, # so we're shortcutting. if hasattr(request, "accumulator"): writeTable(request) request.finish() else: return streaming.streamOut(writeTable, request)
[docs]def getSyncResource(request, service, name): if request.uwsArgs["request"]=="getCapabilities": return vosi.VOSICapabilityRenderer(request, service) else: return TAPQueryResource(service)
[docs]def parseTAPParameters(request): """returns a coreArgs instance for the TAP parameters in request. """ # since this is absolutely not the place to parse the input, but # the way we've implemented TAP we need to do it here, we # operate our context grammar by hand. grammar = base.resolveCrossId("//tap#tap-input-grammar") ri = grammar.parse(request.strargs) list(ri) return ri.getParameters()
# TODO: we should probably define different renderers for sync, # async. The renderer shouldn't have to dispatch # like that.
[docs]class TAPRenderer(grend.ServiceBasedPage): """A renderer speaking all of TAP (including sync, async, and VOSI). Basically, this just dispatches to the sync and async resources. """ name = "tap" urlUse = "base"
[docs] def render(self, request): # The root resource redirects to an info on TAP raise svcs.WebRedirect(self.service.getURL("info", absolute=False))
def _doTAP10CompatCode(self, parameters): """changes request so our TAP 1.1 code keeps working with 1.0 clients. This is called once, at the very start of handling stuff. """ if parameters["responseformat"] is None: parameters["responseformat"] = parameters.pop("format", None)
[docs] def getChild(self, name, request): request.uwsArgs = parseTAPParameters(request) self._doTAP10CompatCode(request.uwsArgs) try: if (getTAPVersion()!= request.uwsArgs.get("version", getTAPVersion())): return uwsactions.ErrorResource({ "msg": "Version mismatch; this service only supports" " TAP version %s."%getTAPVersion(), "type": "ValueError", "hint": ""}) if name==b'sync': return getSyncResource( request, self.service, name.decode("ascii")) elif name==b'async': return asyncrender.getAsyncResource( request, tap.WORKER_SYSTEM, "tap", self.service, None) elif name==b'availability': res = vosi.VOSIAvailabilityRenderer(request, self.service) elif name==b'capabilities': res = vosi.VOSICapabilityRenderer(request, self.service) elif name==b'tables': res = vosi.VOSITablesetRenderer(request, self.service) elif name==b'examples': from gavo.web import examplesrender res = examplesrender.Examples(request, self.service) elif name==b'authcheck': from gavo.web import adminrender res = adminrender.AuthcheckRenderer(request, self.service) else: raise svcs.UnknownURI("Bad TAP child '%s'"%(utils.debytify(name))) return res except svcs.UnknownURI: raise except base.Error as ex: # see flagError in protocols.uws for the reason for the next if if not isinstance(ex, (base.ValidationError, uws.JobNotFound)): base.ui.notifyError("TAP error") return uwsactions.ErrorResource(ex) raise common.UnknownURI("Bad TAP child '%s'"%utils.debytify(name))