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)4548 """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 107202109 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"118120 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)125127 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 = False134136 """raises a ValidationError if literal cannot be deserialised into 137 an acceptable value for self. 138 """ 139 self._parse(literal)140142 """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 None182 183 @classmethod185 """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)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 key224227 """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 268294270 """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 self282284 """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.")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 """330308 self.inputTD, self.args, self.rawArgs = inputTD, args, rawArgs 309 base.MetaMixin.__init__(self)310 313 316 317 @classmethod319 """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)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 """405341 self.locator = "(internal)" 342 grammars.RowIterator.__init__(self, grammar, 343 utils.CaseSemisensitiveDict(sourceToken), 344 **kwargs)345347 """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]361363 """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)")377379 # 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 402408 """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 454457 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 |