Package gavo :: Package dm :: Module vodml
[frames] | no frames]

Source Code for Module gavo.dm.vodml

  1  """ 
  2  Parsing VO-DML files and validating against the rules obtained in this way. 
  3   
  4  Validation is something we expect to do only fairly rarely, so none of 
  5  this code is expected to be efficient. 
  6  """ 
  7   
  8  #c Copyright 2008-2019, the GAVO project 
  9  #c 
 10  #c This program is free software, covered by the GNU GPL.  See the 
 11  #c COPYING file in the source distribution. 
 12   
 13   
 14  from gavo import base 
 15  from gavo import utils 
 16  from gavo.utils import ElementTree 
 17  from gavo.votable import V 
 18   
 19   
 20  KNOWN_MODELS = { 
 21  # maps the canonical prefix to the file name within resources/dm and 
 22  # (for now) the canonical URI (which isn't available anywhere else so far). 
 23          "NDcube": ("CubeDM-1.0.vo-dml.xml",  
 24                  "http://www.ivoa.net/dm/CubeDM-1.0.vo-dml.xml"), 
 25          "ds": ("DatasetMetadata-1.0.vo-dml.xml", 
 26                  "http://www.ivoa.net/dm/DatasetMetadata-1.0.vo-dml.xml"), 
 27          "ivoa": ("IVOA.vo-dml.xml", "http://www.ivoa.net/dm/ivoa.vo-dml.xml"), 
 28          "vo-dml": ("VO-DML.vo-dml.xml", "http://www.ivoa.net/dm/VO-DML.vo-dml.xml"), 
 29          "dachstoy": ("dachstoy.vo-dml.xml", "http://docs.g-vo.org/dachstoy"), 
 30          "stc2": ("STC.vo-dml.xml", "http://www.ivoa.net/dm/STC.vo-dml.xml"), 
 31          "geojson": ("geojson.vo-dml.xml", "http://docs.g-vo.org/geojson"), 
 32  } 
33 34 35 -def openModelFile(prefix):
36 """returns an open file for the VO-DML file corresponding to prefix. 37 38 This will raise a NotFoundError for an unknown prefix. 39 """ 40 try: 41 fName, _ = KNOWN_MODELS[prefix] 42 except KeyError: 43 raise base.NotFoundError(prefix, "VO-DML file for prefix", 44 "data models known to DaCHS", hint="This can happen if there" 45 " are new data models around or if data providers have defined" 46 " custom data models. If this error was fatal during VOTable" 47 " processing, please report it as an error; bad data model" 48 " annotation should not be fatal in DaCHS.") 49 return base.openDistFile("dm/"+fName)
50
51 52 -class Model(object):
53 """a vo-dml model. 54 55 These are usually constructed using the fromPrefix constructor, 56 which uses a built-in mapping from well-known prefix to VO-DML file 57 to populate the model. 58 """ 59 60 # non-well-known models can be fed in through fromFile; they well 61 # be entered here and can then be obtained through fromPrefix 62 # as long as the don't clash with KNOWN_MODELS. 63 64 _modelsReadFromFile = {} 65
66 - def __init__(self, prefix, dmlTree):
67 self.prefix = prefix 68 self.title = self.version = None 69 self.version = self.url = None 70 self.description = None 71 self.dmlTree = dmlTree 72 self.__idIndex = None 73 if self.dmlTree: 74 self._getModelMeta()
75 76 @classmethod
77 - def fromPrefix(cls, prefix):
78 """returns a VO-DML model for a well-known prefix. 79 80 User code should typically use the getModelFromPrefix function. 81 """ 82 if prefix in cls._modelsReadFromFile: 83 return cls._modelsReadFromFile[prefix] 84 85 inF = openModelFile(prefix) 86 try: 87 try: 88 res = cls(prefix, ElementTree.parse(inF)) 89 # as long as we can't get the URL from the XML, patch it in here 90 res.url = KNOWN_MODELS[prefix][1] 91 return res 92 except Exception as ex: 93 raise base.ui.logOldExc( 94 base.StructureError("Failure to parse VO-DML for prefix %s: %s"%( 95 prefix, repr(ex)))) 96 finally: 97 inF.close()
98 99 @classmethod
100 - def fromFile(cls, src, srcURL="http //not.given/invalid"):
101 """returns a VO-DML model from src. 102 103 src can either be a file name (interpreted relative to the root 104 of DaCHS' VO-DML repository) or an open file (which will be closed 105 as a side effect of this function). 106 107 This is intended for documents using non-standard models with custom 108 prefixes (i.e., not known to DaCHS). 109 """ 110 if hasattr(src, "read"): 111 inF = src 112 else: 113 inF = openModelFile(src) 114 115 try: 116 tree = ElementTree.parse(inF) 117 prefix = tree.find("name").text 118 res = cls(prefix, tree) 119 res.url = srcURL 120 121 if prefix not in KNOWN_MODELS: 122 cls._modelsReadFromFile[prefix] = res 123 return res 124 finally: 125 inF.close()
126 127 @property
128 - def idIndex(self):
129 """returns a dictionary mapping vodmlids to elementtree objects. 130 """ 131 if self.__idIndex is None: 132 self.__idIndex = self._createIndex() 133 return self.__idIndex
134
135 - def _createIndex(self):
136 """returns a dictionary mapping vodml-ids to elementtree objects. 137 138 Use the idIndex property rather than this function, as the former will 139 cache the dicts. 140 """ 141 res = {} 142 for element in self.dmlTree.getroot().iter(): 143 id = element.find("vodml-id") 144 if id is not None: 145 res[id.text] = element 146 return res
147
148 - def _getModelMeta(self):
149 """sets some metadata on the model from the parsed VO-DML. 150 151 This will fail silently (i.e., the metadata will remain on its 152 default). 153 154 Metadata obtained so far includes: title, version, description, 155 """ 156 try: 157 self.title = self.dmlTree.find("title").text 158 self.version = self.dmlTree.find("version").text 159 self.description = self.dmlTree.find("description").text 160 except AttributeError: 161 # probably the VO-DML file is bad; just fall through to 162 # non-validatable model. 163 pass
164
165 - def _resolveNonLocalVODMLId(self, id):
166 """returns an etree Element pointed to by the VO-DML id 167 168 This is a helper for getByVODMLId and works by sucessively 169 trying shorter pieces of id. 170 171 This returns None on a failure rather than raising an exception 172 (because it's really a helper for getByVODMLId). 173 """ 174 parts = id.split(".") 175 for splitPoint in range(len(parts)-1, 0, -1): 176 newId = ".".join(parts[:splitPoint]) 177 if newId in self.idIndex: 178 # this should be an attribute definition. Now follow 179 # the chain of attribute names to the end 180 att = self.idIndex[newId] 181 thisType = resolveVODMLId( 182 att.find("datatype").find("vodml-ref").text) 183 184 for attName in parts[splitPoint:]: 185 att = getAttributeDefinition(thisType, attName) 186 thisType = resolveVODMLId( 187 att.find("datatype").find("vodml-ref").text) 188 return att
189 # fall through on failure 190
191 - def getByVODMLId(self, vodmlId):
192 """returns the element with vodmlId. 193 194 This raises a NotFoundError for elements that are not present. 195 196 This can be used with or without the prefix. The prefix is not 197 validated, though. 198 """ 199 if ":" in vodmlId: 200 vodmlId = vodmlId.split(":", 1)[1] 201 202 # We may have to follow ids through several documents based on types. 203 # First try to directly find the element. 204 if vodmlId in self.idIndex: 205 return self.idIndex[vodmlId] 206 207 res = self._resolveNonLocalVODMLId(vodmlId) 208 if res: 209 return res 210 else: 211 raise base.NotFoundError(vodmlId, "data model element", 212 self.prefix+" data model")
213
214 - def getAttributeMeta(self, vodmlId):
215 """returns a metadata dictionary for a VO-DML element with vodmlId. 216 217 This includes datatype add description. If vodmlId points to 218 the value of a quantity, the associate unit and ucd attributes 219 are returned as well. 220 221 If the vodmlId cannot be found, a NotFoundError is raised. 222 """ 223 raise NotImplementedError("We've not yet figured out how this is" 224 " supposed to work.")
225
226 - def getVOT(self, ctx, instance):
227 """returns xmlstan for a VOTable declaration of this DM. 228 """ 229 return V.MODEL[ 230 V.NAME(version=self.version)[self.prefix], 231 V.URL[self.url]]
232
233 234 @utils.memoized 235 -def getModelForPrefix(prefix):
236 """returns a vodml.Model instance for as well-known VODML prefix. 237 238 This caches models for prefixes and thus should usually be used 239 from user code. 240 241 Note that this currently will currently return some stand-in shim 242 for unknown prefixes. That behaviour will change to become a 243 NotFoundError exception when there's actually useful data models. 244 """ 245 try: 246 return Model.fromPrefix(prefix) 247 except base.NotFoundError: 248 res = Model(prefix, ElementTree.fromstring( 249 """<junk><title>DaCHS standin model</title> 250 <description>This is used by DaCHS during the old west 251 days of VO DM development. Any annotation using this will 252 not be interoperable.</description> 253 <version>invalid</version></junk>""")) 254 res.url = "urn:dachsjunk:not-model:"+prefix 255 return res
256
257 -def getAttributeDefinition(typeDef, attName):
258 """returns the attribute definition for attName in typeDef as an etree. 259 260 This raises a NotFoundError if the attribute is not found. 261 """ 262 for attribute in typeDef.findall("attribute"): 263 if attribute.find("name").text==attName: 264 return attribute 265 raise base.NotFoundError(attName, "Attribute", 266 "VO-DML type "+typeDef.find("name").text)
267
268 269 -def resolveVODMLId(vodmlId):
270 """returns an etree element corresponding to the prefixed vodmlId. 271 272 Of course, this only works if vodmlId has a well-known prefix. 273 """ 274 prefix, id = vodmlId.split(":", 1) 275 return getModelForPrefix(prefix).getByVODMLId(id)
276