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
Ejemplo n.º 3
0
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
Ejemplo n.º 5
0
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)