1 """
2 Resource mixins.
3 """
4
5
6
7
8
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
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
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
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
67
68 null=None,
69 copyable=True, strip=True, default=base.NotGiven)
70
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
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
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
161
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
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
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
245
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
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
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:
283 self.curName = None
284 return self
285 else:
286
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
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 """
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
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
336
337
339 def mixesIn(instance, mixinRef):
340 return mixinRef in instance.mixin
341 yield "mixesIn", mixesIn
342
344 return ("A mixin reference, typically to support certain protocol."
345 " See Mixins_.")
346
347 - def create(self, parent, ctx, name):
348
349
350 return _MixinParser(parent, self)
351