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

Source Code for Module gavo.dm.dmrd

  1  """ 
  2  Writing annotations in RDs. 
  3   
  4  This module provides the glue between annotations (typically in SIL) 
  5  and the rest of the RDs.  It provides the ResAnnotation struct, which 
  6  contains the SIL, and the makeAttributeAnnotation function at is a factory 
  7  for attribute annotations. 
  8  """ 
  9   
 10  #c Copyright 2008-2019, the GAVO project 
 11  #c 
 12  #c This program is free software, covered by the GNU GPL.  See the 
 13  #c COPYING file in the source distribution. 
 14   
 15   
 16  import functools 
 17  import itertools 
 18   
 19  from gavo import base 
 20  from gavo.dm import annotations 
 21  from gavo.dm import common 
 22  from gavo.dm import sil 
 23   
 24   
25 -class SynthesizedRoles(base.Structure):
26 """DM annotation copied and adapted to a new table. 27 28 This is a stand-in for DataModelRoles in tables not parsed from 29 XMLs. Their DM structure is defined through references the columns and 30 params make to the annotations their originals had. 31 32 These have no attributes but just arrange for the new annotations 33 to be generated. 34 """ 35 name_ = "_synthesizedRoles" 36
37 - def synthesizeAnnotations(self, rd, ctx):
38 # don't overwrite existing annotations (usually, they 39 # will be the result of a previous run when multiple 40 # annotations are present). 41 if self.parent.annotations: 42 return 43 44 annotationMap = {} 45 46 # First, construct a map from column/param annotations in 47 # the old annotations to new columns and params. As a 48 # side effect, the dmRoles of the new items are cleared. 49 for item in itertools.chain(self.parent.params, self.parent.columns): 50 if item.parent!=self.parent: 51 # Don't annotate things that don't belong to us. 52 # This is going to hide problems big time. But touching these 53 # certainly is worse, and if people are missing annotations, 54 # they'll probably notice they have to copy these. 55 continue 56 57 if isinstance(item.dmRoles, list): 58 # item has been processed before, probably as part of another 59 # DM declaration. No need to repeat that. 60 continue 61 62 for annotation in item.dmRoles.oldRoles: 63 annotationMap[annotation()] = item 64 item.dmRoles = [] 65 66 # Then, collect the annotations we have to copy 67 oldInstances = set(ann.instance() for ann in annotationMap) 68 69 # tell the instances to copy themselves, replacing references 70 # accordingly. 71 newInstances = [] 72 for oldInstance in oldInstances: 73 newInstances.append( 74 oldInstance.copyWithAnnotationMap( 75 annotationMap, self.parent, None)) 76 self.parent.annotations = newInstances
77
78 - def completeElement(self, ctx):
79 ctx.addExitFunc(self.synthesizeAnnotations) 80 self._completeElementNext(SynthesizedRoles, ctx)
81 82
83 -class DataModelRoles(base.Structure):
84 """an annotation of a table in terms of data models. 85 86 The content of this element is a Simple Instance Language clause. 87 """ 88 89 # We defer the parsing of the contained element to (hopefully) the 90 # end of the parsing of the RD to enable forward references with 91 # too many headaches (stubs don't cut it: we (may) need to know types). 92 # 93 # There's an additional complication in that we may want to 94 # access parsed annotations while parsing other annotations 95 # (e.g., when processing foreign keys). 96 # To allow the thing to "parse itself" in such situations, we do 97 # all the crazy magic with the _buildAnnotation function. 98 name_ = "dm" 99 100 _sil = base.DataContent(description="SIL (simple instance language)" 101 " annotation.", copyable=True) 102
103 - def getAnnotation(self, roleName, container, instance):
104 return annotations.GroupRefAnnotation(roleName, self.parse(), instance)
105
106 - def completeElement(self, ctx):
107 def _buildAnnotation(): 108 try: 109 self._parsedAnnotation = sil.getAnnotation( 110 self.content_, getAnnotationMaker(self.parent)) 111 self.parent.annotations.append(self._parsedAnnotation) 112 except Exception as ex: 113 raise base.ui.logOldExc(base.StructureError(str(ex), 114 pos=self.getSourcePosition())) 115 self._buildAnnotation = lambda: None
116 self._buildAnnotation = _buildAnnotation 117 118 ctx.addExitFunc(lambda rd, ctx: self._buildAnnotation()) 119 self._completeElementNext(DataModelRoles, ctx)
120
121 - def parse(self):
122 """returns a parsed version of the embedded annotation. 123 124 Do not call this while the RD is still being built, as dm 125 elements may contain forward references, and these might 126 not yet be available during the parse. 127 """ 128 self._buildAnnotation() 129 return self._parsedAnnotation
130
131 - def copy(self, newParent, ctx):
132 # we use the general mechanism used for recovering annotations from 133 # columns and params in tables here so we're independent of 134 # changes in columns. 135 return SynthesizedRoles(newParent).finishElement(ctx)
136 137
138 -class DataModelRolesAttribute(base.StructListAttribute):
139 """an attribute allowing data model annotation using SIL. 140 141 It will also give an annotations attribute on the instance, and a 142 getAnnotationsOfType method letting you pull out a specific annotation. 143 144 For situation where an existing annotation should be copied from 145 annotations coming in through columns and/or params, the attribute 146 also gives an updateAnnotationFromChildren method. It will do 147 nothing if some annotation already exists. 148 """
149 - def __init__(self):
150 base.StructListAttribute.__init__(self, 151 "dm", 152 childFactory=DataModelRoles, 153 description="Annotations for data models.", 154 copyable=True)
155
156 - def iterParentMethods(self):
157 def iterAnnotationsOfType(instance, typeName): 158 """returns the first annotation of the type passed. 159 """ 160 for ann in instance.annotations: 161 if ann.type==typeName: 162 yield ann
163 164 yield ("iterAnnotationsOfType", iterAnnotationsOfType) 165 166 def updateAnnotationFromChildren(instance): 167 roleSynthesizer = SynthesizedRoles(instance) 168 roleSynthesizer.synthesizeAnnotations(None, None)
169 yield ("updateAnnotationFromChildren", updateAnnotationFromChildren) 170 171
172 -def makeAttributeAnnotation(container, instance, attName, attValue):
173 """returns a typed annotation for attValue within container. 174 175 When attValue is a literal, this is largely trivial. If it's a reference, 176 this figures out what it points to and creates an annotation of 177 the appropriate type (e.g., ColumnAnnotation, ParamAnnotation, etc). 178 179 container in current DaCHS should be a TableDef or something similar; 180 this function expects at least a getByName function and an rd attribute. 181 182 instance is the root of the current annotation. Complex objects should 183 keep a (weak) reference to that. We don't have parent links in 184 our dm trees, and without a reference to the root there's no 185 way we can go "up". 186 187 This is usually used as a callback from within sil.getAnnotation and 188 expects Atom and Reference instances as used there. 189 """ 190 if isinstance(attValue, sil.Atom): 191 return common.AtomicAnnotation(attName, attValue, instance=instance) 192 193 elif isinstance(attValue, sil.Reference): 194 # try name-resolving first (resolveId only does id resolving on 195 # unadorned strings) 196 try: 197 res = container.getByName(attValue) 198 except base.NotFoundError: 199 if container.rd: 200 res = base.resolveId(container.rd, attValue, instance=container) 201 else: 202 raise 203 204 if not hasattr(res, "getAnnotation"): 205 raise base.StructureError("Element %s cannot be referenced" 206 " within a data model."%repr(res)) 207 208 return res.getAnnotation(attName, container, instance) 209 210 else: 211 assert False
212 213
214 -def getAnnotationMaker(container):
215 """wraps makeAttributeAnnotationMaker such that names are resolved 216 within container. 217 """ 218 return functools.partial(makeAttributeAnnotation, container)
219