Package gavo :: Package rscdef :: Module mixins
[frames] | no frames]

Source Code for Module gavo.rscdef.mixins

  1  """ 
  2  Resource mixins. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import threading 
 12   
 13  from gavo import base 
 14  from gavo.base import activetags 
 15  from gavo.rscdef import procdef 
 16   
 17   
 18  __docformat__ = "restructuredtext en" 
 19   
 20   
21 -class ProcessEarly(procdef.ProcApp):
22 """A code fragment run by the mixin machinery when the structure 23 being worked on is being finished. 24 25 Within processEarly, you can access: 26 27 - the structure the mixin is applied to as "substrate" 28 - the mixin parameters as "mixinPars" 29 - the parse context as "context" 30 31 (the context is particularly handy for context.resolveId) 32 """ 33 name_ = "processEarly" 34 formalArgs = "context, substrate, mixinPars"
35 36
37 -class ProcessLate(procdef.ProcApp):
38 """A code fragment run by the mixin machinery when the parser parsing 39 everything exits. 40 41 Within processLate, you can access: 42 43 - the structure mixed in as "substrate", 44 - the root structure of the whole parse tree as root, 45 - the parse context as "context", 46 - and the mixin parameters (a dictionary) as "mixinPars". 47 """ 48 name_ = "processLate" 49 formalArgs = "substrate, root, context, mixinPars"
50 51
52 -class MixinPar(procdef.RDParameter):
53 """A parameter definition for mixins. 54 55 The (optional) body provides a default for the parameter. 56 """ 57 name_ = "mixinPar" 58 59 _expr = base.DataContent(description="The default for the parameter." 60 " A __NULL__ here does not directly mean None/NULL, but since the" 61 " content will frequently end up in attributes, it will ususally work" 62 " as presetting None." 63 " An empty content means a non-preset parameter, which must be filled" 64 " in applications. The magic value __EMPTY__ allows presetting an" 65 " empty string.", 66 # mixinPars must not evaluate __NULL__; this stuff ends up in 67 # macro expansions, where an actual None is not desirable. 68 null=None, 69 copyable=True, strip=True, default=base.NotGiven) 70
71 - def validate(self):
72 self._validateNext(MixinPar) 73 if len(self.key)<2: 74 raise base.LiteralParseError("name", self.key, hint="Names of" 75 " mixin parameters must have at least two characters (since" 76 " they are exposed as macros")
77 78
79 -class LateEvents(activetags.EmbeddedStream):
80 """An event stream played back by a mixin when the substrate is being 81 finalised (but before the early processing). 82 """ 83 name_ = "lateEvents"
84 85
86 -class MixinDef(activetags.ReplayBase):
87 """A definition for a resource mixin. 88 89 Resource mixins are resource descriptor fragments typically rooted 90 in tables (though it's conceivable that other structures could 91 grow mixin attributes as well). 92 93 They are used to define and implement certain behaviours components of 94 the DC software want to see: 95 96 - products want to be added into their table, and certain fields are required 97 within tables describing products 98 - tables containing positions need some basic machinery to support scs. 99 - siap needs quite a bunch of fields 100 101 Mixins consist of events that are played back on the structure 102 mixing in before anything else happens (much like original) and 103 two procedure definitions, viz, processEarly and processLate. 104 These can access the structure that has the mixin as substrate. 105 106 processEarly is called as part of the substrate's completeElement 107 method. processLate is executed just before the parser exits. This 108 is the place to fix up anything that uses the table mixed in. Note, 109 however, that you should be as conservative as possible here -- you 110 should think of DC structures as immutable as long as possible. 111 112 Programmatically, you can check if a certain table mixes in 113 something by calling its mixesIn method. 114 115 Recursive application of mixins, even to seperate objects, will deadlock. 116 """ 117 name_ = "mixinDef" 118 119 _doc = base.UnicodeAttribute("doc", description="Documentation for" 120 " this mixin", strip=False) 121 _events = base.StructAttribute("events", 122 childFactory=activetags.EmbeddedStream, 123 description="Events to be played back into the structure mixing" 124 " this in at mixin time.", copyable=True, 125 default=base.NotGiven) 126 _lateEvents = base.StructAttribute("lateEvents", 127 childFactory=LateEvents, 128 description="Events to be played back into the structure mixing" 129 " this in at completion time.", copyable=True, 130 default=base.NotGiven) 131 _processEarly = base.StructAttribute("processEarly", 132 default=None, 133 childFactory=ProcessEarly, 134 description="Code executed at element fixup.", 135 copyable=True) 136 _processLate = base.StructAttribute("processLate", 137 default=None, 138 childFactory=ProcessLate, 139 description="Code executed resource fixup.", 140 copyable=True) 141 _pars = base.UniquedStructListAttribute("pars", 142 childFactory=MixinPar, 143 uniqueAttribute="key", 144 description="Parameters available for this mixin.", 145 copyable=True) 146 _original = base.OriginalAttribute() 147
148 - def completeElement(self, ctx):
149 # we want to double-expand macros in mixins. Thus, reset all 150 # value/expanded events to plain values 151 if self.events: 152 self.events.unexpandMacros() 153 if self.lateEvents: 154 self.lateEvents.unexpandMacros() 155 156 # This lock protects against multiple uses of applyTo. This is 157 # necessary because during replay, we have macroExpansions and 158 # macroParent reflect a concrete application's context. 159 self.applicationLock = threading.Lock() 160 self._completeElementNext(MixinDef, ctx)
161
162 - def _defineMacros(self, fillers, destination):
163 """creates attributes macroExpansions and parentMacroPackage used by 164 execMacros. 165 166 Within mixins, you can use macros filled by mixin parameters or 167 expanded by the substrate. This information is local to a concrete 168 mixin application. Hence, applyTo calls this method, and the 169 attributes created are invalid for any subsequent or parallel applyTo 170 calls. Therefore, applyTo acquires the applicationLock before 171 calling this. 172 """ 173 self.parentMacroPackage = None 174 if hasattr(destination, "execMacro"): 175 self.parentMacroPackage = destination 176 177 self.macroExpansions = {} 178 for p in self.pars: 179 if p.key in fillers: 180 self.macroExpansions[p.key] = fillers.pop(p.key) 181 elif p.isDefaulted(): 182 self.macroExpansions[p.key] = p.content_ 183 else: 184 raise base.StructureError("Mixin parameter %s mandatory"%p.key) 185 if fillers: 186 raise base.StructureError("The attribute(s) %s is/are not allowed" 187 " on this mixin"%(",".join(fillers)))
188
189 - def execMacro(self, macName, args):
190 if macName in self.macroExpansions: 191 return self.macroExpansions[macName] 192 try: 193 if self.parentMacroPackage: 194 return self.parentMacroPackage.execMacro(macName, args) 195 except base.MacroError: 196 raise base.MacroError( 197 "No macro \\%s available in this mixin or substrate."%(macName), 198 macName)
199
200 - def applyTo(self, destination, ctx, fillers={}):
201 """replays the stored events on destination and arranges for processEarly 202 and processLate to be run. 203 """ 204 with self.applicationLock: 205 self._defineMacros(fillers.copy(), destination) 206 if self.events: 207 self.replay(self.events.events_, destination, ctx) 208 209 if self.processEarly is not None: 210 self.processEarly.compile(destination)(ctx, destination, 211 self.macroExpansions) 212 213 if self.processLate is not None: 214 def procLate(rootStruct, parseContext): 215 self.processLate.compile(destination)( 216 destination, rootStruct, parseContext, self.macroExpansions)
217 ctx.addExitFunc(procLate) 218 219 if self.lateEvents: 220 origComplete = destination.completeElement 221 def newComplete(ctx): 222 with self.applicationLock: 223 self._defineMacros(fillers.copy(), destination) 224 self.replay(self.lateEvents.events_, destination, ctx) 225 origComplete(ctx)
226 destination.completeElement = newComplete 227
228 - def applyToFinished(self, destination):
229 """applies the mixin to an object already parsed. 230 231 Late callbacks will only be executed if destination has an rd 232 attribute; if that is the case, this rd's idmap will be amended 233 with anything the mixin comes up with. 234 """ 235 rd = None 236 if hasattr(destination, "rd"): 237 rd = destination.rd 238 239 ctx = base.ParseContext() 240 if rd is not None: 241 ctx.idmap = destination.rd.idmap 242 self.applyTo(destination, ctx) 243 244 # we don't keep the application lock for this; applyToFinished 245 # is more of a debugging thing, so we don't worry too much. 246 if self.lateEvents: 247 self.replay(self.lateEvents.events_, destination, ctx) 248 249 if rd is not None: 250 ctx.runExitFuncs(rd)
251 252
253 -class _MixinParser(base.Parser):
254 """A parser for structured mixin references. 255 256 These can contain attribute definitions for any parameter of the 257 mixin referenced. 258 """
259 - def __init__(self, parent, parentAttr):
260 self.parent, self.parentAttr = parent, parentAttr 261 self.fillers = {} 262 self.curName = None # this is non-None while parsing a child element
263
264 - def start_(self, ctx, name, value):
265 if self.curName is not None: 266 raise base.StructureError("%s elements cannot have %s children in" 267 " mixins."%(self.curName, name)) 268 self.curName = name 269 return self
270
271 - def value_(self, ctx, name, value):
272 if name=="content_": 273 if self.curName: 274 self.fillers[self.curName] = value 275 else: 276 self.fillers["mixin name"] = value.strip() 277 else: 278 self.fillers[name] = value 279 return self
280
281 - def end_(self, ctx, name, value):
282 if self.curName: # end parsing parameter binding 283 self.curName = None 284 return self 285 else: # end of mixin application, run the mixin and hand control back to 286 # mixin parent 287 if "mixin name" not in self.fillers: 288 raise base.StructureError("Empty mixin children not allowed") 289 mixinRef = self.fillers.pop("mixin name") 290 self.parentAttr.feed(ctx, self.parent, mixinRef, fillers=self.fillers) 291 return self.parent
292 293
294 -class MixinAttribute(base.SetOfAtomsAttribute):
295 """An attribute defining a mixin. 296 297 This currently is only offered on tables, though in principle we could 298 have it anywhere now, but we'd want some compatibility checking 299 then. 300 301 This is never copyable since this would meaning playing the same 302 stuff into an object twice. 303 304 This means trouble for magic scripts (in particular processLate); e.g., 305 if you copy a table mixing in products, the data element for that table 306 will not receive the product table. Goes to show the whole product 307 mess is ugly and needs a good idea. 308 """
309 - def __init__(self, **kwargs):
310 kwargs["itemAttD"] = base.UnicodeAttribute("mixin", strip=True) 311 kwargs["description"] = kwargs.get("description", 312 "Reference to a mixin this table should contain; you can" 313 " give mixin parameters as attributes or children.") 314 kwargs["copyable"] = False 315 base.SetOfAtomsAttribute.__init__(self, "mixin", **kwargs)
316
317 - def _insertCompleter(self, instance, completerFunc):
318 """arranges completerFunc to be called as part of instance's 319 completeElement callbacks. 320 """ 321 origComplete = instance.completeElement 322 def mixinCompleter(ctx): 323 completerFunc() 324 origComplete(ctx)
325 instance.completeElement = mixinCompleter
326
327 - def feed(self, ctx, instance, mixinRef, fillers={}):
328 """feeds the immediate elements and schedules the rest of 329 actions to be taken in time. 330 """ 331 mixin = ctx.resolveId(mixinRef, instance=instance, forceType=MixinDef) 332 base.SetOfAtomsAttribute.feed(self, ctx, instance, mixinRef) 333 mixin.applyTo(instance, ctx, fillers)
334 335 # no need to override feedObject: On copy and such, replay has already 336 # happened. 337
338 - def iterParentMethods(self):
339 def mixesIn(instance, mixinRef): 340 return mixinRef in instance.mixin
341 yield "mixesIn", mixesIn 342
343 - def makeUserDoc(self):
344 return ("A mixin reference, typically to support certain protocol." 345 " See Mixins_.")
346
347 - def create(self, parent, ctx, name):
348 # since mixins may contain parameters, we need a custom parser 349 # when mixin is a child. 350 return _MixinParser(parent, self)
351