Source code for gavo.protocols.ssap

"""
The SSAP core and supporting code.

"""

#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 itertools

from gavo import base
from gavo import rsc
from gavo import rscdef
from gavo import svcs
from gavo.protocols import datalink
from gavo.svcs import outputdef


MS = base.makeStruct


[docs]class SSADescriptor(datalink.ProductDescriptor): """SSA descriptors have ssaRow and limits attributes. These both reference SSA results. ssaRow is the first result of the query, which also provides the accref. limits is a table.Limits instance for the total result set. Warning: limits will be None if this is constructed with fromSSARow. """ ssaRow = None
[docs] @classmethod def fromSSARow(cls, ssaRow, paramDict): """returns a descriptor from a row in an ssa table and the params of that table. Don't use this; the limits attribute will be {} for these. """ paramDict.update(ssaRow) ssaRow = paramDict res = cls.fromAccref(ssaRow["ssa_pubDID"], ssaRow['accref']) res.ssaRow = ssaRow res.pubDID = ssaRow["ssa_pubDID"] res.limits = {} return res
[docs] @classmethod def fromSSAResult(cls, ssaResult): """returns a descriptor from an SSA query result (an InMemoryTable instance). """ if not ssaResult.rows: raise base.NotFoundError("any", "row", "ssa result for SODA block" " generation") res = cls.fromSSARow(ssaResult.rows[0], ssaResult.getParamDict()) res.limits = ssaResult.getLimits() res.pubDID = None return res
def _combineRowIntoOne(ssaRows): """makes a "total row" from ssaRows. In the resulting row, minima and maxima are representative of the whole result set, and enumerated columns are set-valued. This is useful when generating parameter metadata. """ # XXX TODO: shouldn't this be made more generic by using something like # in user.info? if not ssaRows: raise base.ReportableError("Datalink meta needs at least one result row") totalRow = ssaRows[0].copy() totalRow["mime"] = set([totalRow["mime"]]) calibs = set() for row in ssaRows[1:]: if row["ssa_specstart"]<totalRow["ssa_specstart"]: totalRow["ssa_specstart"] = row["ssa_specstart"] if row["ssa_specend"]>totalRow["ssa_specend"]: totalRow["ssa_specend"] = row["ssa_specend"] totalRow["mime"].add(row["mime"]) calibs.add(row.get("ssa_fluxcalib", None)) totalRow["collect_calibs"] = set(c for c in calibs if c is not None) return totalRow
[docs]def getDatalinkCore(dlSvc, ssaTable): """returns a datalink core adapted for ssaTable. dlSvc is the datalink service, ssaTable a non-empty SSA result table. """ allowedRendsForStealing = ["dlget"] #noflake: for stealVar downstack desc = SSADescriptor.fromSSAResult(ssaTable) return dlSvc.core.adaptForDescriptors( svcs.getRenderer("dlget"), [desc], None)
[docs]class SSAPCore(svcs.DBCore): """A core doing SSAP queries. This core knows about metadata queries, version negotiation, and dispatches on REQUEST. Thus, it may return formatted XML data under certain circumstances. Interpreted Properties: * previews: If set to "auto", the core will automatically add a preview column and fill it with the URL of the products-based preview. Other values are not defined. """ name_ = "ssapCore" outputTableXML = """ <outputTable verbLevel="30"> <property name="virtual">True</property> <FEED source="//ssap#coreOutputAdditionals"/> </outputTable>""" previewColumn = base.parseFromString(svcs.OutputField, '<outputField name="preview" type="text"' ' ucd="meta.ref.url;meta.preview" tablehead="Preview"' ' description="URL of a preview for the dataset"' ' select="NULL" displayHint="type=product" verbLevel="15"/>') ############### Implementation of the service operations
[docs] def onElementComplete(self): super().onElementComplete() if self.getProperty("previews", default=False)=="auto": self.outputTable.columns.append(self.previewColumn)
def _run_getTargetNames(self, service, inputTable, queryMeta): with base.getTableConn() as conn: table = rsc.TableForDef(self.queriedTable, create=False, connection=conn) destTD = base.makeStruct(outputdef.OutputTableDef, parent_=self.queriedTable.parent, id="result", onDisk=False, columns=[self.queriedTable.getColumnByName("ssa_targname")]) res = table.getTableForQuery(destTD, "", distinct=True) res.noPostprocess = True return res def _addPreviewLinks(self, resultTable): for row in resultTable: if "preview" in row and row["preview"] is None: row["preview"] = row["accref"]+"?preview=True" def _run_queryData(self, service, inputTable, queryMeta): limitThroughTOP = inputTable.getParam("TOP") if limitThroughTOP and limitThroughTOP<queryMeta["dbLimit"]: queryMeta["dbLimit"] = limitThroughTOP res = svcs.DBCore.run(self, service, inputTable, queryMeta) if self.getProperty("previews", default=False)=="auto": self._addPreviewLinks(res) if service.hasProperty("datalink"): # backwards compatibility: The datalink property on the service # can name a datalink service. We don't want that any more, but # for now copy it to the queried table (where it should be) # XXX Deprecate around version 1.3 try: res.getMeta("_associatedDatalinkService", raiseOnFail=True) except base.NoMetaKey: # No datalink metadata on the table yet, generate one from the property. res.addMeta( "_associatedDatalinkService.serviceId", service.getProperty("datalink")) res.addMeta( "_associatedDatalinkService.idColumn", "ssa_pubDID") return res ################ the main dispatcher
[docs] def run(self, service, inputTable, queryMeta): defaultRequest = service.getProperty("defaultRequest", "") requestType = (inputTable.getParam("REQUEST") or defaultRequest).upper() if requestType=="QUERYDATA": return self._run_queryData(service, inputTable, queryMeta) elif requestType=="GETTARGETNAMES": return self._run_getTargetNames(service, inputTable, queryMeta) else: raise base.ValidationError("Missing or invalid value for REQUEST.", "REQUEST", hint="According to SSAP, you can only have queryData here;" " as an extension, this service also understands getTargetNames.")
_VIEW_COLUMNS_CACHE = []
[docs]def iterViewColumns(context): """returns a list of column objects for building the SSA view mixin's columns. The argument is the DaCHS RD parse context. This is probably only useful for the //ssap#view mixin. The argument is that mixin's context. This could go if we drop the hcd and mixc mixins and instead have a normal STREAM with the columns, as it's really only necessary to make columns from the stupid params and remove their defaults. This will always return the same column objects -- don't change them. """ if not _VIEW_COLUMNS_CACHE: dontCopyAtts = frozenset(["value", "content_"]) protoTable = context.getById("instance") _VIEW_COLUMNS_CACHE.append([MS(rscdef.Column, **c.getCopyableAttributes(ignoreKeys=dontCopyAtts)) for c in itertools.chain( protoTable.columns, protoTable.params)]) return _VIEW_COLUMNS_CACHE[0]