| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 Description and handling of inputs to services.
3
4 This module in particular describes the InputKey, the primary means
5 of describing input widgets and their processing.
6
7 They are collected in contextGrammars, entities creating inputArgs
8 items.
9 """
10
11 #c Copyright 2008-2019, the GAVO project
12 #c
13 #c This program is free software, covered by the GNU GPL. See the
14 #c COPYING file in the source distribution.
15
16
17 import itertools
18
19 from gavo import base
20 from gavo import grammars
21 from gavo import rscdef
22 from gavo import votable
23 from gavo import utils
24 from gavo.rscdef import column
25 from gavo.svcs import dalipars
26 from gavo.svcs import pql
27 from gavo.svcs import vizierexprs
28
29 MS = base.makeStruct
30
31
32 _RENDERER_ADAPTORS = {
33 'form': vizierexprs.adaptInputKey,
34 'pql': pql.adaptInputKey,
35 'dali': dalipars.adaptInputKey,
36 }
39 """returns a function that returns input keys adapted for renderer.
40
41 The function returns None if no adapter is necessary. This
42 only takes place for inputKeys within a buildFrom condDesc.
43 """
44 return _RENDERER_ADAPTORS.get(renderer.parameterStyle)
45
48 """A description of a piece of input.
49
50 Think of inputKeys as abstractions for input fields in forms, though
51 they are used for services not actually exposing HTML forms as well.
52
53 Some of the DDL-type attributes (e.g., references) only make sense here
54 if columns are being defined from the InputKey.
55
56 Properties evaluated:
57
58 * defaultForForm -- a value entered into form fields by default
59 (be stingy with those; while it's nice to not have to set things
60 presumably right for almost everyone, having to delete stuff
61 you don't want over and over is really annoying).
62 * adaptToRenderer -- a true boolean literal here causes the param
63 to be adapted for the renderer (e.g., float could become vizierexpr-float).
64 You'll usually not want this, because the expressions are
65 generally evaluated by the database, and the condDescs do the
66 adaptation themselves. This is mainly for rare situations like
67 file uploads in custom cores.
68 * notForRenderer -- a renderer name for which this inputKey is suppressed
69 * onlyForRenderer -- a renderer name for which this inputKey will be
70 preserved; it will be dropped for all others.
71 """
72 name_ = "inputKey"
73
74 # XXX TODO: make widgetFactory and showItems properties.
75 _widgetFactory = base.UnicodeAttribute("widgetFactory", default=None,
76 description="A python expression for a custom widget"
77 " factory for this input,"
78 " e.g., 'Hidden' or 'widgetFactory(TextArea, rows=15, cols=30)'",
79 copyable=True)
80 _showItems = base.IntAttribute("showItems", default=3,
81 description="Number of items to show at one time on selection widgets.",
82 copyable=True)
83 _inputUnit = base.UnicodeAttribute("inputUnit", default=None,
84 description="Override unit of the table column with this.",
85 copyable=True)
86 _std = base.BooleanAttribute("std", default=False,
87 description="Is this input key part of a standard interface for"
88 " registry purposes?",
89 copyable=True)
90 _multiplicity = base.UnicodeAttribute("multiplicity", default=None,
91 copyable=True,
92 description="Set"
93 " this to single to have an atomic value (chosen at random"
94 " if multiple input values are given),"
95 " forced-single to have an atomic value"
96 " and raise an exception if multiple values come in, or"
97 " multiple to receive lists. On the form renderer, this is"
98 " ignored, and the values are what nevow formal passes in."
99 " If not given, it is single unless there is a values element with"
100 " options, in which case it's multiple.")
101
102 # Don't validate meta for these -- while they are children
103 # of validated structures (services), they don't need any
104 # meta at all. This should go as soon as we have a sane
105 # inheritance hierarchy for tables.
106 metaModel = None
107
109 self._completeElementNext(InputKey, ctx)
110 if self.restrictedMode and self.widgetFactory:
111 raise base.RestrictedElement("widgetFactory")
112
113 if self.multiplicity is None:
114 if self.isEnumerated():
115 self.multiplicity = "multiple"
116 else:
117 self.multiplicity = "single"
118
120 self._onElementCompleteNext(InputKey)
121 # compute scaling if an input unit is given
122 self.scaling = None
123 if self.inputUnit:
124 self.scaling = base.computeConversionFactor(self.inputUnit, self.unit)
125
127 if self.parent and hasattr(self.parent, "required"):
128 # children of condDescs inherit their requiredness
129 # (unless defaulted)
130 self.required = self.parent.required
131 # but if there's a default, never require an input
132 if self.value:
133 self.required = False
134
136 """raises a ValidationError if literal cannot be deserialised into
137 an acceptable value for self.
138 """
139 self._parse(literal)
140
142 """parses some input for this input key.
143
144 This takes into account multiplicities, type conversions, and
145 all the remaining horrors.
146
147 It will return a list or a single value, depending on multiplity.
148 """
149 if not inputList:
150 if self.value is not None:
151 inputList = [self.value]
152
153 elif self.values and self.values.default:
154 inputList = [self.values.default]
155
156 elif self.required:
157 raise base.ValidationError(
158 "Required parameter %s missing."%self.name,
159 self.name)
160
161 else:
162 return None
163
164 if self.multiplicity=="forced-single" and len(inputList)>1:
165 raise base.MultiplicityError(
166 "Inputs for the parameter %s must not have more than"
167 " one value; hovever, %s was passed in."%(
168 self.name,
169 str(inputList)),
170 colName=self.name)
171
172 if self.multiplicity=="multiple":
173 vals = [v for v in (self._parse(val) for val in inputList)
174 if v is not None]
175 return vals or None
176
177 else:
178 if inputList:
179 return self._parse(inputList[-1])
180 else:
181 return None
182
183 @classmethod
185 """returns an InputKey for query input to column.
186 """
187 if isinstance(column, InputKey):
188 if kwargs:
189 return column.change(**kwargs)
190 else:
191 return column
192
193 instance = cls(None)
194 instance.feedObject("original", column)
195
196 for k,v in kwargs.iteritems():
197 instance.feed(k, v)
198 if not "required" in kwargs:
199 instance.feedObject("required", False)
200 instance.dmRoles = rscdef.OldRoles(column.dmRoles)
201 return instance.finishElement(None)
202
205 """filters inputKeys in key, only returning those compatible with
206 rendName.
207
208 adaptor is is a function taking and returning an inputKey that is used
209 for input keys with an adaptToRenderer property.
210 """
211 for key in keys:
212 if key.getProperty("onlyForRenderer", None) is not None:
213 if key.getProperty("onlyForRenderer")!=rendName:
214 continue
215 if key.getProperty("notForRenderer", None) is not None:
216 if key.getProperty("notForRenderer")==rendName:
217 continue
218
219 if base.parseBooleanLiteral(key.getProperty("adaptToRenderer", "False")
220 ) and adaptor:
221 key = adaptor(key)
222
223 yield key
224
227 """an input for a core.
228
229 These aren't actually proper tables but actually just collection of
230 the param-like inputKeys. They serve as input declarations for cores
231 and services (where services derive their inputTDs from the cores' ones by
232 adapting them to the current renderer. Their main use is for the derivation
233 of contextGrammars.
234
235 They can carry metadata, though, which is sometimes convenient when
236 transporting information from the parameter parsers to the core.
237
238 For the typical dbCores (and friends), these are essentially never
239 explicitly defined but rather derived from condDescs.
240
241 Do *not* read input values by using table.getParam. This will only
242 give you one value when a parameter has been given multiple times.
243 Instead, use the output of the contextGrammar (inputParams in condDescs).
244 Only there you will have the correct multiplicities.
245 """
246 name_ = "inputTable"
247
248 _inputKeys = rscdef.ColumnListAttribute("inputKeys",
249 childFactory=InputKey,
250 description='Input parameters for this table.',
251 copyable=True)
252
253 _groups = base.StructListAttribute("groups",
254 childFactory=rscdef.Group,
255 description="Groups of inputKeys (this is used for form UI formatting).",
256 copyable=True)
257
258 _exclusive = base.BooleanAttribute("exclusive",
259 description="If true, context grammars built from this will"
260 " raise an error if contexts passed in have keys not defined"
261 " by this table",
262 default=False,
263 copyable=True)
264 _rd = rscdef.RDAttribute()
265
268
270 """returns an inputTD tailored for renderer.
271
272 This is discussed in svcs.core's module docstring.
273 """
274 newKeys = list(
275 filterInputKeys(self.inputKeys, renderer.name,
276 getRendererAdaptor(renderer)))
277
278 if newKeys!=self.inputKeys:
279 return self.change(inputKeys=newKeys, parent_=self)
280 else:
281 return self
282
284 """returns a column name from a queried table of the embedding core,
285 if available.
286
287 This is a convenicence hack that lets people reference columns from
288 a TableBasedCore by their simple, non-qualified names.
289 """
290 if self.parent and hasattr(self.parent, "queriedTable"):
291 return self.parent.queriedTable.resolveName(ctx, name)
292 raise base.NotFoundError(id, "Element with id or name", "name path",
293 hint="There is not queried table this name could be resolved in.")
294
297 """A container for core arguments.
298
299 There's inputTD, which reference the renderer-adapted input table,
300 and args, the ContextGrammar processed input. For kicks, we also
301 have rawArgs, which is the contextGrammar's input (if you find
302 you're using it, tell us; that's pointing to a problem on our side).
303
304 getParam(name) -> value and getParamDict() -> dict methods are
305 present for backward compatibility.
306 """
308 self.inputTD, self.args, self.rawArgs = inputTD, args, rawArgs
309 base.MetaMixin.__init__(self)
310
313
316
317 @classmethod
319 """returns a CoreArgs instance built from an inputDD and
320 ContextGrammar-parseable rawArgs.
321
322 contextGrammar can be overridden, e.g., to cache or to add
323 extra, non-core keys.
324 """
325 if contextGrammar is None:
326 contextGrammar = MS(ContextGrammar, inputTD=inputTD)
327 ri = contextGrammar.parse(rawArgs)
328 _ = list(ri) #noflake: ignored value
329 return cls(inputTD, ri.getParameters(), rawArgs)
330
333 """is a row iterator over "contexts", i.e. single dictionary-like objects.
334
335 The source token expected here can be a request.args style dictionary
336 with lists of strings as arguments, or a parsed dictionary from nevow
337 formal. Non-list literals in the latter are packed into a list to ensure
338 consistent behaviour.
339 """
341 self.locator = "(internal)"
342 grammars.RowIterator.__init__(self, grammar,
343 utils.CaseSemisensitiveDict(sourceToken),
344 **kwargs)
345
347 """this turns lonely values in the sourceToken into lists so we
348 don't have to special-case nevow formal dicts.
349
350 Do not rely on this any more; it's deeply broken (which becomes
351 obvious when we actually have array-valued input) and it's scheduled
352 for removal.
353 """
354 for key, value in self.sourceToken.iteritems():
355 if value is None:
356 self.sourceToken[key] = []
357 else:
358 if not isinstance(value, list):
359 base.ui.notifyWarning("Non-list input to ContextGrammar: %s"%key)
360 self.sourceToken[key] = [value]
361
363 """makes sure sourceToken has no keys not mentioned in the grammar.
364
365 Here, we assume case-insensitive arguments for now. If we ever
366 want to change that... oh, my.
367
368 This is only exectued for grammars with rejectExtras.
369 """
370 inKeys = set(n.lower() for n in self.sourceToken)
371 expectedKeys = set(k.name.lower() for k in self.grammar.iterInputKeys())
372 if inKeys-expectedKeys:
373 raise base.ValidationError("The following parameter(s) are"
374 " not accepted by this service: %s"%",".join(
375 sorted(inKeys-expectedKeys)),
376 "(various)")
377
379 # we don't really return any rows, but this is where our result
380 # dictionary is built.
381 self._ensureListValues()
382 if self.grammar.rejectExtras:
383 self._ensureNoExtras()
384
385 self.coreArgs = {}
386
387 for ik in self.grammar.iterInputKeys():
388 self.locator = "param %s"%ik.name
389 try:
390 self.coreArgs[ik.name] = ik.computeCoreArgValue(
391 self.sourceToken.get(ik.name))
392 except votable.BadVOTableLiteral as ex:
393 raise base.ValidationError("Bad parameter literal %s (%s)"%(
394 self.sourceToken.get(ik.name), ex),
395 ik.name)
396
397 if False:
398 yield {} # contextGrammars yield no rows.
399
402
405
408 """A grammar for web inputs.
409
410 The source tokens for context grammars are dictionaries; these
411 are either typed dictionaries from nevow formal, where the values
412 usually are atomic, or, preferably, the dictionaries of lists
413 from request.args.
414
415 ContextGrammars never yield rows, so they're probably fairly useless
416 in normal cirumstances.
417
418 In normal usage, they just yield a single parameter row,
419 corresponding to the source dictionary possibly completed with
420 defaults, where non-requried input keys get None defaults where not
421 given. Missing required parameters yield errors.
422
423 This parameter row honors the multiplicity specification, i.e., single or
424 forced-single are just values, multiple are lists. The content are
425 *parsed* values (using the InputKeys' parsers).
426
427 Since most VO protocols require case-insensitive matching of parameter
428 names, matching of input key names and the keys of the input dictionary
429 is attempted first literally, then disregarding case.
430 """
431 name_ = "contextGrammar"
432
433 _inputTD = base.ReferenceAttribute("inputTD",
434 default=base.NotGiven,
435 description="The input table from which to take the input keys",
436 copyable=True)
437
438 _inputKeys = base.StructListAttribute("inputKeys",
439 childFactory=InputKey,
440 description="Extra input keys not defined in the inputTD. This"
441 " is used when services want extra input processed by them rather"
442 " than their core.",
443 copyable=True)
444
445 _original = base.OriginalAttribute("original")
446
447 rowIterator = ContextRowIterator
448
449 rejectExtras = False
450
454
457
458
459 _OPTIONS_FOR_MULTIS = {
460 "forced-single": ", single=True, forceUnique=True",
461 "single": ", single=True",
462 "multiple": "",
463 }
464
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 2 07:29:09 2019 | http://epydoc.sourceforge.net |