"""
"Output drivers" for various formats, for the use of form-like renderers.
TODO: Tar and the output format widget should go somewhere else; the
rest should be done by RESPONSEFORMAT and formats. Then this module
should die.
"""
#c Copyright 2008-2023, the GAVO project <gavo@ari.uni-heidelberg.de>
#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.template import tags as T
from gavo import base
from gavo import formats
from gavo import utils
from gavo.formats import csvtable #noflake: format registration
from gavo.formats import jsontable #noflake: format registration
from gavo.formats import fitstable #noflake: format registration
from gavo.formats import texttable #noflake: format registration
from gavo.formats import geojson #noflake: format registration
from gavo.formal import types as formaltypes
from gavo.formal.util import render_cssid
from gavo.protocols import products
from gavo.svcs import customwidgets
from gavo.svcs import streaming
from gavo.web import producttar
__docformat__ = "restructuredtext en"
[docs]class ServiceResult(object):
"""A base class for objects producing formatted output.
They are basically wrappers for _formatOutput, which
will usually be used as sources for streaming.streamOut.
All methods on these objects are class methods -- they are never
instantiated.
Deriving classes can override
- _formatOutput(result, request, queryMeta) -- receives the service result
and arranges for it to be written.
- canWrite(colSeq) -- a that returns true when a seq of columns
can be serialized by this service result.
- code -- an identifier used in HTTP query strings to select the format
- label (optional) -- a string that is used in HTML forms to select
the format (defaults to label).
- compute -- if False, at least the form renderer will not run
the service (this is when you just return a container).
"""
compute = True
code = None
label = None
@classmethod
def _formatOutput(cls, res, request, queryMeta):
return b""
[docs] @classmethod
def getLabel(cls):
if cls.label is not None:
return cls.label
return cls.code
[docs] @classmethod
def canWrite(cls, colSeq):
return True
[docs]class VOTableResult(ServiceResult):
"""A ResultFormatter for VOTables.
The VOTables come as attachments, i.e., if all goes well the form
will just stand as it is.
"""
code = "VOTable"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
if base.getMetaText(data.getPrimaryTable(), "_queryStatus"
)=="OVERFLOW":
fName = "truncated_votable.xml"
else:
fName = "votable.xml"
request.setHeader("content-type", base.votableType)
request.setHeader('content-disposition',
'attachment; filename=%s'%fName)
return streaming.streamVOTable(request, data, queryMeta)
[docs]class FITSTableResult(ServiceResult):
"""returns data as a FITS binary table.
"""
code = "FITS"
label = "FITS table"
[docs] @classmethod
def getTargetName(cls, data):
if base.getMetaText(data.getPrimaryTable(), "_queryStatus"
)=="OVERFLOW":
return "truncated_data.fits", "application/x-fits"
else:
return "data.fits", "application/x-fits"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
return threads.deferToThread(fitstable.makeFITSTableFile, data
).addCallback(cls._serveFile, data, request)
@classmethod
def _serveFile(cls, filePath, data, request):
name, mime = cls.getTargetName(data)
request.setHeader("content-type", mime)
request.setHeader('content-disposition',
'attachment; filename=%s'%name)
def writeData(dest):
try:
with open(filePath, "rb") as f:
utils.cat(f, dest)
finally:
os.unlink(filePath)
return streaming.streamOut(writeData, request)
[docs]class TSVResponse(ServiceResult):
code = "TSV"
label = "Text (with Tabs)"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
request.setHeader("content-type", "text/tab-separated-values")
def produceData(destFile):
content = texttable.getAsText(data)
destFile.write(content)
return streaming.streamOut(produceData, request, queryMeta)
[docs]class TextResponse(ServiceResult):
code = "txt"
label = "Text (fixed columns)"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
request.setHeader("content-type", "text/plain")
def produceData(destFile):
formats.formatData("txt", data,
request, acquireSamples=False)
return streaming.streamOut(produceData, request, queryMeta)
[docs]class CSVResponse(ServiceResult):
code = "CSV"
label = "CSV"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
request.setHeader('content-disposition',
'attachment; filename=table.csv')
request.setHeader("content-type", "text/csv;header=present")
def produceData(destFile):
formats.formatData("csv", data,
request, acquireSamples=False)
return streaming.streamOut(produceData, request, queryMeta)
[docs]class JsonResponse(ServiceResult):
code = "JSON"
label = "JSON"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
request.setHeader('content-disposition',
'attachment; filename=table.json')
request.setHeader("content-type", "application/json")
def produceData(destFile):
formats.formatData("json", data,
request, acquireSamples=False)
return streaming.streamOut(produceData, request, queryMeta)
[docs]class TarResponse(ServiceResult):
"""delivers a tar of products requested.
"""
code = "tar"
@classmethod
def _formatOutput(cls, data, request, queryMeta):
return producttar.getTarMaker().deliverProductTar(
data, request, queryMeta)
[docs] @classmethod
def canWrite(cls, colSeq):
if products.getProductColumns(colSeq):
return True
return False
################# Helpers
_getFormat = utils.buildClassResolver(ServiceResult,
list(globals().values()), key=lambda obj: obj.code)