class InputTable(rscdef.TableDef): """an input table for a core. For the standard cores, these have no rows but only params, with the exception of ComputedCore, which can build program input from rows. Typically, however, the input table definition is made from a core's condDescs and thus never explicitely defined. In these cases, adaptForRenderer becomes relevant. This is for when one renderer, e.g., form, needs to expose a different interface than another, e.g., a protocol-based renderer. SCS is a good example, where the form renderer has a single argument for the position. """ name_ = "inputTable" _params = rscdef.ColumnListAttribute("params", childFactory=InputKey, description='Input parameters for' ' this table.', copyable=True, aliases=["param"]) def adaptForRenderer(self, renderer): """returns an inputTable tailored for renderer. This is discussed in svcs.core's module docstring. """ newParams = list( filterInputKeys(self.params, renderer.name, getRendererAdaptor(renderer))) if newParams != self.params: return self.change(params=newParams, parent_=self) else: return self
class ContextGrammar(grammars.Grammar): """A grammar for web inputs. These are almost exclusively in InputDDs. They hold InputKeys defining what they take from the context. For DBCores, the InputDDs are generally defined implicitely via CondDescs. Thus, only for other cores will you ever need to bother with ContextGrammars (unless you're going for special effects). The source tokens for context grammars are dictionaries; these are either typed dictionaries from nevow, where the values usually are atomic, or, preferably, the dictionaries of lists from request.args. ContextGrammars only yield rows if there's a rowKey defined. In that case, an outer join of all other parameters is returned; with rowKey defined, the input keys are obtained from the table's columns. In normal usage, they just yield a single parameter row, corresponding to the source dictionary possibly completed with defaults, where non-requried input keys get None defaults where not given. Missing required parameters yield errors. Since most VO protocols require case-insensitive matching of parameter names, matching of input key names and the keys of the input dictionary is attempted first literally, then disregarding case. """ name_ = "contextGrammar" _inputTable = base.ReferenceAttribute( "inputTable", default=base.NotGiven, description="The table that is to be built using this grammar", copyable=True) _inputKeys = rscdef.ColumnListAttribute( "inputKeys", childFactory=InputKey, description="Input keys this context grammar should parse." " These must not be given if there is an input table defined.") _rowKey = base.UnicodeAttribute( "rowKey", default=base.NotGiven, description="The name of a key that is used to generate" " rows from the input", copyable=True) _rejectExtras = base.BooleanAttribute( "rejectExtras", default=False, description="If true, the grammar will reject extra input parameters." " Note that for form-based services, there *are* extra parameters" " not declared in the services' input tables. Right now," " contextGrammar does not ignore those.", copyable=True) _original = base.OriginalAttribute("original") rowIterator = ContextRowIterator def onElementComplete(self): if self.inputTable is not base.NotGiven: if self.inputKeys != []: raise base.StructureError( "InputKeys and inputTable must not" " both be given in a context grammar") else: if self.rowKey: self.inputKeys = [ InputKey.fromColumn(c) for c in self.inputTable.columns ] else: self.inputKeys = self.inputTable.params else: columns = [] if self.rowKey: columns = self.inputKeys self.inputTable = MS(InputTable, params=self.inputKeys, columns=columns) self.defaults = {} for ik in self.iterInputKeys(): if not ik.required: self.defaults[ik.name] = None if ik.value is not None: self.defaults[ik.name] = ik.value self._onElementCompleteNext(ContextGrammar) def iterInputKeys(self): for ik in self.inputKeys: yield ik
class CondDesc(base.Structure): """A query specification for cores talking to the database. CondDescs define inputs as a sequence of InputKeys (see `Element InputKey`_). Internally, the values in the InputKeys can be translated to SQL. """ name_ = "condDesc" _inputKeys = rscdef.ColumnListAttribute("inputKeys", childFactory=inputdef.InputKey, description="One or more InputKeys defining the condition's input.", copyable=True) _silent = base.BooleanAttribute("silent", default=False, description="Do not produce SQL from this CondDesc. This" " can be used to convey meta information to the core. However," " in general, a service is a more appropriate place to deal with" " such information, and thus you should prefer service InputKeys" " to silent CondDescs.", copyable=True) _required = base.BooleanAttribute("required", default=False, description="Reject queries not filling the InputKeys of this CondDesc", copyable=True) _fixedSQL = base.UnicodeAttribute("fixedSQL", default=None, description="Always insert this SQL statement into the query. Deprecated.", copyable=True) _buildFrom = base.ReferenceAttribute("buildFrom", description="A reference to a column or an InputKey to define" " this CondDesc", default=None) _phraseMaker = base.StructAttribute("phraseMaker", default=None, description="Code to generate custom SQL from the input keys", childFactory=PhraseMaker, copyable=True) _combining = base.BooleanAttribute("combining", default=False, description="Allow some input keys to be missing when others are given?" " (you want this for pseudo-condDescs just collecting random input" " keys)", # (and I wish I had a better idea) copyable="True") _group = base.StructAttribute("group", default=None, childFactory=rscdef.Group, description="Group child input keys in the input table (primarily" " interesting for web forms, where this grouping is shown graphically;" " Set the style property to compact to have a one-line group there)") _joiner = base.UnicodeAttribute("joiner", default="OR", description="When yielding multiple fragments, join them" " using this operator (probably the only thing besides OR is" " AND).", copyable=True) _original = base.OriginalAttribute() def __init__(self, parent, **kwargs): base.Structure.__init__(self, parent, **kwargs) # copy parent's resolveName if present for buildFrom resolution if hasattr(self.parent, "resolveName"): self.resolveName = self.parent.resolveName def __repr__(self): return "<CondDesc %s>"%",".join(ik.name for ik in self.inputKeys) @classmethod def fromInputKey(cls, ik, **kwargs): return base.makeStruct(CondDesc, inputKeys=[ik], **kwargs) @classmethod def fromColumn(cls, col, **kwargs): return base.makeStruct(cls, buildFrom=col, **kwargs) @property def name(self): """returns some key for uniqueness of condDescs. """ # This is necessary for ColumnLists that are used # for CondDescs as well. Ideally, we'd do this on an # InputKeys basis and yield their names (because that's what # formal counts on), but it's probably not worth the effort. return "+".join([f.name for f in self.inputKeys]) def completeElement(self, ctx): if self.buildFrom and not self.inputKeys: # use the column as input key; special renderers may want # to do type mapping, but the default is to have plain input self.inputKeys = [inputdef.InputKey.fromColumn(self.buildFrom)] self._completeElementNext(CondDesc, ctx) def expand(self, *args, **kwargs): """hands macro expansion requests (from phraseMakers) upwards. This is to the queried table if the parent has one (i.e., we're part of a core), or to the RD if not (i.e., we're defined within an rd). """ if hasattr(self.parent, "queriedTable"): return self.parent.queriedTable.expand(*args, **kwargs) else: return self.parent.rd.expand(*args, **kwargs) def _makePhraseDefault(self, ignored, inputKeys, inPars, outPars, core): # the default phrase maker uses whatever the individual input keys # come up with. for ik in self.inputKeys: yield base.getSQLForField(ik, inPars, outPars) # We only want to compile the phraseMaker if actually necessary. # condDescs may be defined within resource descriptors (e.g., in # scs.rd), and they can't be compiled there (since macros may # be missing); thus, we dispatch on the first call. def _getPhraseMaker(self): try: return self.__compiledPhraseMaker except AttributeError: if self.phraseMaker is not None: val = self.phraseMaker.compile() else: val = self._makePhraseDefault self.__compiledPhraseMaker = val return self.__compiledPhraseMaker makePhrase = property(_getPhraseMaker) def _isActive(self, inPars): """returns True if the dict inPars contains input to all our input keys. """ for f in self.inputKeys: if f.name not in inPars: return False return True def inputReceived(self, inPars, queryMeta): """returns True if all inputKeys can be filled from inPars. As a side effect, inPars will receive defaults form the input keys if there are any. """ if not self._isActive(inPars): return False keysFound, keysMissing = [], [] for f in self.inputKeys: if inPars.get(f.name) is None: keysMissing.append(f) else: if f.value!=inPars.get(f.name): # non-defaulted keysFound.append(f) if not keysMissing: return True # keys are missing. That's ok if none were found and we're not required if not self.required and not keysFound: return False if self.required: raise base.ValidationError("is mandatory but was not provided.", colName=keysMissing[0].name) # we're optional, but a value was given and others are missing if not self.combining: raise base.ValidationError("When you give a value for %s," " you must give value(s) for %s, too"%(keysFound[0].getLabel(), ", ".join(k.name for k in keysMissing)), colName=keysMissing[0].name) return True def asSQL(self, inPars, sqlPars, queryMeta): if self.silent or not self.inputReceived(inPars, queryMeta): return "" res = list(self.makePhrase( self, self.inputKeys, inPars, sqlPars, self.parent)) sql = base.joinOperatorExpr(self.joiner, res) if self.fixedSQL: sql = base.joinOperatorExpr(self.joiner, [sql, self.fixedSQL]) return sql def adaptForRenderer(self, renderer): """returns a changed version of self if renderer suggests such a change. This only happens if buildFrom is non-None. The method must return a "defused" version that has buildFrom None (or self, which will do because core.adaptForRenderer stops adapting if the condDescs are stable). The adaptors may also raise a Replace exception and return a full CondDesc; this is done, e.g., for spoints for the form renderer, since they need two input keys and a completely modified phrase. """ if not self.buildFrom: return self adaptor = inputdef.getRendererAdaptor(renderer) if adaptor is None: return self try: newInputKeys = [] for ik in self.inputKeys: newInputKeys.append(adaptor(ik)) if self.inputKeys==newInputKeys: return self else: return self.change(inputKeys=newInputKeys, buildFrom=None) except base.Replace, ex: return ex.newOb
class DatalinkCoreBase(svcs.Core, base.ExpansionDelegator): """Basic functionality for datalink cores. This is pulled out of the datalink core proper as it is used without the complicated service interface sometimes, e.g., by SSAP. """ _descriptorGenerator = base.StructAttribute( "descriptorGenerator", default=base.NotGiven, childFactory=DescriptorGenerator, description="Code that takes a PUBDID and turns it into a" " product descriptor instance. If not given," " //soda#fromStandardPubDID will be used.", copyable=True) _metaMakers = base.StructListAttribute( "metaMakers", childFactory=MetaMaker, description="Code that takes a data descriptor and either" " updates input key options or yields related data.", copyable=True) _dataFunctions = base.StructListAttribute( "dataFunctions", childFactory=DataFunction, description="Code that generates of processes data for this" " core. The first of these plays a special role in that it" " must set descriptor.data, the others need not do anything" " at all.", copyable=True) _dataFormatter = base.StructAttribute( "dataFormatter", default=base.NotGiven, childFactory=DataFormatter, description="Code that turns descriptor.data into a nevow resource" " or a mime, content pair. If not given, the renderer will be" " returned descriptor.data itself (which will probably not usually" " work).", copyable=True) _inputKeys = rscdef.ColumnListAttribute( "inputKeys", childFactory=svcs.InputKey, description="A parameter to one of the proc apps (data functions," " formatters) active in this datalink core; no specific relation" " between input keys and procApps is supposed; all procApps are passed" " all argments. Conventionally, you will write the input keys in" " front of the proc apps that interpret them.", copyable=True) # The following is a hack complemented in inputdef.makeAutoInputDD. # We probably want some other way to do this (if we want to do it # at all) rejectExtras = True def completeElement(self, ctx): if self.descriptorGenerator is base.NotGiven: self.descriptorGenerator = MS( DescriptorGenerator, procDef=base.resolveCrossId("//soda#fromStandardPubDID")) if self.dataFormatter is base.NotGiven: self.dataFormatter = MS( DataFormatter, procDef=base.caches.getRD("//datalink").getById( "trivialFormatter")) self.inputKeys.append( MS(svcs.InputKey, name="ID", type="text", ucd="meta.id;meta.main", multiplicity="multiple", std=True, description="The pubisher DID of the dataset of interest")) if self.inputTable is base.NotGiven: self.inputTable = MS(svcs.InputTable, params=self.inputKeys) # this is a cheat for service.getTableSet to pick up the datalink # table. If we fix this for TAP, we should fix it here, too. self.queriedTable = base.caches.getRD("//datalink").getById( "dlresponse") self._completeElementNext(DatalinkCoreBase, ctx) def getMetaForDescriptor(self, descriptor): """returns a pair of linkDefs, inputKeys for a datalink desriptor and this core. """ linkDefs, inputKeys, errors = [], self.inputKeys[:], [] for metaMaker in self.metaMakers: try: for item in metaMaker.compile(self)(self, descriptor): if isinstance(item, LinkDef): linkDefs.append(item) elif isinstance(item, DatalinkFault): errors.append(item) else: inputKeys.append(item) except Exception, ex: if base.DEBUG: base.ui.notifyError( "Error in datalink meta generator %s: %s" % (metaMaker, repr(ex))) base.ui.notifyError("Failing source: \n%s" % metaMaker.getFuncCode()) errors.append( DatalinkFault.Fault( descriptor.pubDID, "Unexpected failure while creating" " datalink: %s" % utils.safe_str(ex))) return linkDefs, inputKeys, errors
class OutputTableDef(rscdef.TableDef): """A table that has outputFields for columns. """ name_ = "outputTable" # Don't validate meta for these -- while they are children # of validated structures (services), they don't need any # meta at all. This should go as soon as we have a sane # inheritance hierarchy for tables. metaModel = None _cols = rscdef.ColumnListAttribute( "columns", childFactory=OutputField, description="Output fields for this table.", aliases=["column"], copyable=True) _verbLevel = base.IntAttribute( "verbLevel", default=None, description="Copy over columns from fromTable not" " more verbose than this.") _autocols = base.StringListAttribute( "autoCols", description="Column names obtained from fromTable; you can use" " shell patterns into the output table's parent table (in a table" " core, that's the queried table; in a service, it's the core's" " output table) here.") def __init__(self, parent, **kwargs): rscdef.TableDef.__init__(self, parent, **kwargs) self.parentTable = None try: # am I in a table-based core? self.parentTable = self.parent.queriedTable except (AttributeError, base.StructureError): # no. pass if not self.parentTable: try: # am I in a service with a core with output table? self.parentTable = self.parent.core.outputTable except (AttributeError, base.StructureError): # no. pass if not self.parentTable: # no suitable column source, use an empty table: self.parentTable = _EMPTY_TABLE self.namePath = None def _adoptColumn(self, sourceColumn): # Do not overwrite existing fields here to let the user # override individually try: self.getColumnByName(sourceColumn.name) except base.NotFoundError: self.feedObject("outputField", OutputField.fromColumn(sourceColumn)) def _addNames(self, ctx, names): # since autoCols is not copyable, we can require # that _addNames only be called when there's a real parse context. if ctx is None: raise base.StructureError("outputTable autocols is" " only available with a parse context") for name in names: self._addName(ctx, name) def _addName(self, ctx, name): """adopts a param or column name into the outputTable. name may be a reference or a param or column name in the parent table (as determined in the constructor, i.e., the queried table of a core or the output table of a service's core. You can also use shell patterns into parent columns. """ if utils.identifierPattern.match(name): refOb = ctx.resolveId(name, self) if refOb.name_ == "param": self.feedObject("param", refOb.copy(self)) else: self._adoptColumn(refOb) else: # it's a shell pattern into parent table for col in self.parentTable: if fnmatch.fnmatch(col.name, name): self._adoptColumn(col) def completeElement(self, ctx): if self.autoCols: self._addNames(ctx, self.autoCols) if self.verbLevel: table = self.parentTable for col in table.columns: if col.verbLevel <= self.verbLevel: self._adoptColumn(col) for par in table.params: if par.verbLevel <= self.verbLevel: self.feedObject("param", par.copy(self)) self._completeElementNext(OutputTableDef, ctx) @classmethod def fromColumns(cls, columns, **kwargs): return rscdef.TableDef.fromColumns( [OutputField.fromColumn(c) for c in columns]) @classmethod def fromTableDef(cls, tableDef, ctx): return cls(None, columns=[OutputField.fromColumn(c) for c in tableDef], forceUnique=tableDef.forceUnique, dupePolicy=tableDef.dupePolicy, primary=tableDef.primary, params=tableDef.params).finishElement(ctx)