1 """
2 Basic handling for embedded procedures.
3 """
4
5
6
7
8
9
10
11 from gavo import base
12 from gavo import utils
13 from gavo.rscdef import common
14 from gavo.rscdef import rmkfuncs
20 """returns all items in sequences uniqued by the items' key attributes.
21
22 The order of the sequence items is not maintained, but items in
23 later sequences override those in earlier ones.
24 """
25 allItems = {}
26 for seq in sequences:
27 for item in seq:
28 allItems[item.key] = item
29 return allItems.values()
30
33 """A base class for parameters.
34 """
35 _name = base.UnicodeAttribute("key", default=base.Undefined,
36 description="The name of the parameter", copyable=True, strip=True,
37 aliases=["name"])
38 _descr = base.NWUnicodeAttribute("description", default=None,
39 description="Some human-readable description of what the"
40 " parameter is about", copyable=True, strip=True)
41 _expr = base.DataContent(description="The default for the parameter."
42 " The special value __NULL__ indicates a NULL (python None) as usual."
43 " An empty content means a non-preset parameter, which must be filled"
44 " in applications. The magic value __EMPTY__ allows presetting an"
45 " empty string.",
46 copyable=True, strip=True, default=base.NotGiven)
47 _late = base.BooleanAttribute("late", default=False,
48 description="Bind the name not at setup time but at applying"
49 " time. In rowmaker procedures, for example, this allows you to"
50 " refer to variables like vars or rowIter in the bindings.")
51
54
60
65
68 """A parameter of a procedure definition.
69
70 Bodies of ProcPars are interpreted as python expressions, in
71 which macros are expanded in the context of the procedure application's
72 parent. If a body is empty, the parameter has no default and has
73 to be filled by the procedure application.
74 """
75 name_ = "par"
82
85 """A binding of a procedure definition parameter to a concrete value.
86
87 The value to set is contained in the binding body in the form of
88 a python expression. The body must not be empty.
89 """
90 name_ = "bind"
91
96
99 """Prescriptions for setting up a namespace for a procedure application.
100
101 You can add names to this namespace you using par(ameter)s.
102 If a parameter has no default and an procedure application does
103 not provide them, an error is raised.
104
105 You can also add names by providing a code attribute containing
106 a python function body in code. Within, the parameters are
107 available. The procedure application's parent can be accessed
108 as parent. All names you define in the code are available as
109 globals to the procedure body.
110
111 Caution: Macros are expanded within the code; this means you
112 need double backslashes if you want a single backslash in python
113 code.
114 """
115 name_ = "setup"
116
117 _code = base.ListOfAtomsAttribute("codeFrags",
118 description="Python function bodies setting globals for the function"
119 " application. Macros are expanded in the context"
120 " of the procedure's parent.",
121 itemAttD=base.UnicodeAttribute("code", description="Python function"
122 " bodies setting globals for the function application. Macros"
123 " are expanded in the context of the procedure's parent.",
124 copyable=True),
125 copyable=True)
126 _pars = base.StructListAttribute("pars", ProcPar,
127 description="Names to add to the procedure's global namespace.",
128 copyable=True)
129 _original = base.OriginalAttribute()
130
132 """returns code that sets our parameters.
133
134 If useLate is true, generate for late bindings. Indent the
135 code by indent. Bindings is is a dictionary overriding
136 the defaults or setting parameter values.
137 """
138 parCode = []
139 for p in self.pars:
140 if p.late==useLate:
141 val = bindings.get(p.key, base.NotGiven)
142 if val is base.NotGiven:
143 val = p.content_
144 parCode.append("%s%s = %s"%(indent, p.key, val))
145 return "\n".join(parCode)
146
148 """returns code doing setup bindings un-indented.
149 """
150 return self._getParSettingCode(False, "", bindings)
151
153 """returns code doing late (in-function) bindings indented with two
154 spaces.
155 """
156 return self._getParSettingCode(True, " ", bindings)
157
158 - def getBodyCode(self):
159 """returns the body code un-indented.
160 """
161 collectedCode = []
162 for frag in self.codeFrags:
163 collectedCode.append(
164 utils.fixIndentation(frag, "", governingLine=1))
165 return "\n".join(collectedCode)
166
167
168 -class ProcDef(base.Structure, base.RestrictionMixin):
169 """An embedded procedure.
170
171 Embedded procedures are python code fragments with some interface defined
172 by their type. They can occur at various places (which is called procedure
173 application generically), e.g., as row generators in grammars, as applys in
174 rowmakers, or as SQL phrase makers in condDescs.
175
176 They consist of the actual actual code and, optionally, definitions like
177 the namespace setup, configuration parameters, or a documentation.
178
179 The procedure applications compile into python functions with special
180 global namespaces. The signatures of the functions are determined by
181 the type attribute.
182
183 ProcDefs are referred to by procedure applications using their id.
184 """
185 name_ = "procDef"
186
187 _code = base.UnicodeAttribute("code", default=base.NotGiven,
188 copyable=True, description="A python function body.")
189 _setup = base.StructListAttribute("setups", ProcSetup,
190 description="Setup of the namespace the function will run in",
191 copyable=True)
192 _doc = base.UnicodeAttribute("doc", default="", description=
193 "Human-readable docs for this proc (may be interpreted as restructured"
194 " text).", copyable=True)
195 _type = base.EnumeratedUnicodeAttribute("type", default=None, description=
196 "The type of the procedure definition. The procedure applications"
197 " will in general require certain types of definitions.",
198 validValues=["t_t", "apply", "rowfilter", "sourceFields", "mixinProc",
199 "phraseMaker", "descriptorGenerator", "dataFunction", "dataFormatter",
200 "metaMaker", "regTest", "iterator", "pargetter"],
201 copyable=True,
202 strip=True)
203 _deprecated = base.UnicodeAttribute("deprecated", default=None,
204 copyable=True, description="A deprecation message. This will"
205 " be shown if this procDef is being compiled.")
206 _original = base.OriginalAttribute()
207
208
216
217 @utils.memoized
219 """returns all parameters used by setup items, where lexically
220 later items override earlier items of the same name.
221 """
222 return unionByKey(*[s.pars for s in self.setups])
223
225 return "\n".join(s.getLateCode(boundNames) for s in self.setups)
226
228 return "\n".join(s.getParCode(boundNames) for s in self.setups)
229
230 - def getBodySetupCode(self, boundNames):
231 return "\n".join(s.getBodyCode() for s in self.setups)
232
235 """An abstract base for procedure applications.
236
237 Deriving classes need to provide:
238
239 - a requiredType attribute specifying what ProcDefs can be applied.
240 - a formalArgs attribute containing a (python) formal argument list
241 - of course, a name_ for XML purposes.
242
243 They can, in addition, give a class attribute additionalNamesForProcs,
244 which is a dictionary that is joined into the global namespace during
245 procedure compilation.
246 """
247 _procDef = base.ReferenceAttribute("procDef", forceType=ProcDef,
248 default=base.NotGiven, description="Reference to the procedure"
249 " definition to apply", copyable=True)
250 _bindings = base.StructListAttribute("bindings", description=
251 "Values for parameters of the procedure definition",
252 childFactory=Binding, copyable=True)
253 _name = base.UnicodeAttribute("name", default=base.NotGiven,
254 description="A name of the proc. ProcApps compute their (python)"
255 " names to be somwhat random strings. Set a name manually to"
256 " receive more easily decipherable error messages. If you do that,"
257 " you have to care about name clashes yourself, though.", strip=True)
258
259 requiredType = None
260
261 additionalNamesForProcs = {}
262
287
292
293 @utils.memoized
295 """returns the setup parameters for the proc app, where procDef
296 parameters may be overridden by self's parameters.
297 """
298 allSetups = []
299 if self.procDef is not base.NotGiven:
300 allSetups.extend(self.procDef.setups)
301 allSetups.extend(self.setups)
302 return unionByKey(*[s.pars for s in allSetups])
303
305 """raises an error if non-defaulted pars of procDef are not filled
306 by the bindings.
307 """
308 bindNames = set(b.key for b in self.bindings)
309 for p in self.getSetupPars():
310 if not p.isDefaulted():
311 if not p.key in bindNames:
312 raise base.StructureError("Parameter %s is not defaulted in"
313 " %s and thus must be bound."%(p.key, self.name))
314 if p.key in bindNames:
315 bindNames.remove(p.key)
316
317 if bindNames:
318 raise base.StructureError("May not bind non-existing parameter(s)"
319 " %s."%(", ".join(bindNames)))
320
324
326
327
328
329 parts = []
330 if self.procDef is not base.NotGiven:
331 parts.append(getattr(self.procDef, methodName)(boundNames))
332
333 parts.append(getattr(ProcDef, methodName)(self, boundNames))
334 return "\n".join(parts)
335
337 return self._combineWithProcDef("getLateSetupCode", boundNames)
338
340 return self._combineWithProcDef("getParSetupCode", boundNames)
341
342 - def getBodySetupCode(self, boundNames):
343 return self._combineWithProcDef("getBodySetupCode", boundNames)
344
352
354 """returns mainSource in a function definition with proper
355 signature including setup of late code.
356 """
357 parts = [self.getLateSetupCode(self._boundNames)]
358 parts.append(mainSource)
359 body = "\n".join(parts)
360 if not body.strip():
361 body = " pass"
362 return "def %s(%s):\n%s"%(self.name, self.formalArgs,
363 body)
364
366 """returns a function definition for this proc application.
367
368 This includes bindings of late parameters.
369
370 Locally defined code overrides code defined in a procDef.
371 """
372 mainCode = ""
373 if self.code is base.NotGiven:
374 if self.procDef is not base.NotGiven:
375 mainCode = self.procDef.getCode()
376 else:
377 mainCode = self.getCode()
378 code = self._getFunctionDefinition(mainCode)
379 if "\\" in code:
380 code = self.parent.expand(code)
381 return code
382
405
410
412 """returns a callable for this procedure application.
413
414 You can pass a different parent; it will then be used to
415 expand macros. If you do not give it, the embedding structure will
416 be used.
417 """
418 if parent is None:
419 parent = self.parent
420 return utils.memoizeOn(parent, self, self._compileForParent, parent)
421