Source code for gavo.stc.utypegen

"""
Generating a utype/value sequence for ASTs.

Yet another insane serialization for an insane data model.  Sigh.

The way we come up with the STC utypes here is described in an IVOA note.

Since the utypes are basically xpaths into STC-X, there is not terribly
much we need to do here.  However, due to STC-X being a nightmare,
certain rules need to be formulated what utypes to generate.

Here, we use UtypeMakers for this.  There's a default UtypeMaker
that implements the basic algorithm of the STC note (in iterUtypes,
a method that yields all utype/value pairs for an STC-X node, which
is a stanxml Element).

To customize what is being generated, define _gener_<child name>
methods, where <child name> is a key within the dictionaries
returned by stanxml.Element's getChildDict method.

To make the definition of the _gener_ methods easier, there's
the handles decorator that you can pass a list of such child names.
"""

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


from gavo import utils
from gavo.stc import common
from gavo.stc import stcxgen
from gavo.stc.stcx import STC


#################### utype maker definition

[docs]def handles(seq): """is a decorator for UtypeMaker methods. It adds a "handles" attribute as evaluated by AutoUtypeMaker. """ def deco(meth): meth.handles = seq return meth return deco
[docs]class UtypeMaker_t(type): """A metaclass to facilite easy definition of UtypeMakers. This metaclass primarily operates on the handles hints left by the decorator. """ def __init__(cls, name, bases, dict): type.__init__(cls, name, bases, dict) cls._createHandlesMethods(list(dict.values())) def _createHandlesMethods(cls, items): for item in items: for name in getattr(item, "handles", ()): setattr(cls, "_gener_"+name, item)
[docs]class UtypeMaker(object, metaclass=UtypeMaker_t): """An object encapsulating information on how to turn a stanxml node into a sequence of utype/value pairs. This is an "universal" base, serving as a simple default. Any class handling specific node types must fill out at least the rootType attribute, giving the utype at which this UtypeMaker should kick in. By default, utype/value pairs are only returned for nonempty element content. To change this, define _gener_<name>(node, prefix) -> iterator methods. The actual pairs are retrieved by calling iterUtypes(node, parentPrefix). """ # attributes that don't get serialized to utypes per spec bannedAttributes = set("id frame_id coord_system_id unit" " pos_angle_unit pos_unit spectral_unit time_unit" " vel_time_unit gen_unit xsi:type ucd xmlns:stc xmlns xmlns:xsi" " xsi:schemaLocation".split()) rootType = None def _generPlain(self, name, child, prefix): childType = utypejoin(prefix, name) maker = _getUtypeMaker(childType) for item in child: for pair in maker.iterUtypes(item, childType): yield pair def _gener__colRef(self, name, child, prefix): yield prefix, child[0]
[docs] def iterUtypes(self, node, prefix): children = node.getChildDict() if node.text_: yield prefix, node.text_ for attName, name in node.iterAttNames(): if name not in self.bannedAttributes: val = getattr(node, attName) if val is not None: yield "%s.%s"%(prefix, name.split(":")[-1]), val for name, child in children.items(): handler = getattr(self, "_gener_"+name, self._generPlain) for pair in handler(name, child, prefix): yield pair
class _NotImplementedUtypeMaker(UtypeMaker): def _generPlain(self, name, child, prefix): raise common.STCNotImplementedError("Cannot create utypes for %s yet."% self.utypeFrag) #################### utype specific makers class _CoordFrameMaker(UtypeMaker): @handles(common.stcRefPositions) def _refPos(self, name, child, prefix): if name!='UNKNOWNRefPos': yield utypejoin(prefix, "ReferencePosition"), name for pair in self._generPlain("ReferencePosition", child, prefix): yield pair
[docs]class TimeFrameMaker(_CoordFrameMaker): rootType = "AstroCoordSystem.TimeFrame" @handles(common.stcTimeScales) def _timeScale(self, name, child, prefix): yield utypejoin(prefix, "TimeScale"), name
[docs]class SpaceFrameMaker(_CoordFrameMaker): rootType = "AstroCoordSystem.SpaceFrame" @handles(common.stcSpaceRefFrames) def _coordFrame(self, name, child, prefix): myPrefix = utypejoin(prefix, "CoordRefFrame") yield myPrefix, name for pair in self._generPlain(None, child, myPrefix): yield pair @handles(common.stcCoordFlavors) def _coordFlavor(self, name, child, prefix): prefix = utypejoin(prefix, "CoordFlavor") yield prefix, name if child: if child[0].coord_naxes!="2": yield utypejoin(prefix, "coord_naxes"), child[0].coord_naxes yield utypejoin(prefix, "handedness"), child[0].handedness
[docs]class RedshiftFrameMaker(_CoordFrameMaker): rootType = "AstroCoordSystem.RedshiftFrame"
[docs] def iterUtypes(self, node, prefix): for pair in _CoordFrameMaker.iterUtypes(self, node, prefix): yield pair
[docs]class SpectralFrameMaker(_CoordFrameMaker): rootType = "AstroCoordSystem.SpectralFrame"
class _TimeValueMaker(UtypeMaker): @handles(["ISOTime", "JDTime", "MJDTime"]) def _absoluteTime(self, name, child, prefix): yield utypejoin(prefix, "xtype"), name for item in child: for pair in self.iterUtypes(item, prefix): yield pair
[docs]class TimeInstantMaker(_TimeValueMaker): rootType = "AstroCoords.Time.TimeInstant"
[docs]class StartTimeMaker(_TimeValueMaker): rootType = "AstroCoordArea.TimeInterval.StartTime"
[docs]class StopTimeMaker(_TimeValueMaker): rootType = "AstroCoordArea.TimeInterval.StopTime"
#################### toplevel code
[docs]def utypejoin(*utypes): return ".".join(u for u in utypes if u)
# A resolver of element names to their handling classes. For most # elements, this is just a plain UtypeMaker. _getUtypeMaker = utils.buildClassResolver( UtypeMaker, list(globals().values()), default=UtypeMaker(), instances=True, key=lambda obj:obj.rootType)
[docs]def getUtypes(ast, includeDMURI=False, suppressXtype=True): """returns a lists of utype/value pairs for an STC AST. If you pass includeDMURI, a utype/value pair for the data model URI will be generated in addition to what comes in from ast. """ cst = stcxgen.astToStan(ast, STC.STCSpec) utypes = [] for utype, val in _getUtypeMaker("").iterUtypes(cst, ""): if val is None or val=='': continue if suppressXtype: if utype.endswith("xtype"): continue utypes.append(("stc:"+utype, val)) if includeDMURI: utypes.append(("stc:DataModel.URI", common.STCNamespace)) utypes.sort() return utypes