class EditCore(standardcores.TableBasedCore):
    """A core that allows POSTing records into database tables.
	"""
    name_ = "editCore"

    _queriedTable = base.ReferenceAttribute(
        "queriedTable",
        default=base.Undefined,
        description="Reference to the table to"
        " be edited",
        forceType=rscdef.TableDef)

    def completeElement(self, ctx):
        if self.outputTable is base.Undefined:
            self.outputTable = base.parseFromString(outputdef.OutputTableDef,
                                                    uploadOutputDef)
        self._completeElementNext(EditCore, ctx)

    def run(self, service, inputTable, queryMeta):
        conn = base.getDBConnection(base.getDBProfile("admin"))
        table = rsc.TableForDef(self.queriedTable, connection=conn)
        table.addRow(inputTable.getParamDict())
        conn.commit()
        conn.close()
        return rsc.TableForDef(self.outputTable, rows=[{"nAffected": 1}])
class FieldRef(base.Structure):
    """A reference to a table column for building simple views.
	"""
    name_ = "columnRef"
    docName_ = "columnRef (view)"
    aliases = ["fieldRef"]

    _srcTable = base.ReferenceAttribute(
        "table",
        default=base.Undefined,
        description="Reference to the table the field comes from.",
        forceType=TableDef)

    _srcCol = base.UnicodeAttribute(
        "key",
        default=base.Undefined,
        description="Column name within the referenced table.",
        aliases=["column"])

    def onElementComplete(self):
        self._onElementCompleteNext(FieldRef)
        if not self.key in self.table:
            raise base.StructureError("No field '%s' in table %s" %
                                      (self.key, self.table.getQName()))

    def getColumn(self):
        return self.table.getColumnByName(self.key)

    def getQName(self):
        name = "%s.%s" % (self.table.getQName(), self.key)
        if "\\" in name:
            name = self.expand(name)
        return name
示例#3
0
class RowsetGrammar(common.Grammar):
    """A grammar handling sequences of tuples.

	To add semantics to the field, it must know the "schema" of the
	data.  This is defined via the table it is supposed to get the input
	from.

	This grammar probably is only useful for internal purposes.
	"""
    name_ = "rowsetGrammar"
    rowIterator = RowsetIterator

    _fieldsFrom = base.ReferenceAttribute(
        "fieldsFrom",
        description="the table defining the columns in the tuples.",
        copyable=True)

    def onElementComplete(self):
        self._onElementCompleteNext(RowsetGrammar)
        self.names = [c.name for c in self.fieldsFrom]
class SDMCore(svcs.Core):
    """A core for making (VO)Tables according to the Spectral Data Model.

	Do *not* use this any more, use datalink to do this.

	Here, the input table consists of the accref of the data to be generated.
	The data child of an SDMVOTCore prescribes how to come up with the
	table.  The output table is the (primary) table of the data instance.

	If you find yourself using this, please let the authors know.  We
	tend to believe SDMCores should no longer be necessary in the presence
	of getData, and hence we might want to remove this at some point.
	"""
    name_ = "sdmCore"
    inputTableXML = """<inputTable id="inFields">
			<inputKey name="accref" type="text" required="True"
				description="Accref of the data within the SSAP table."/>
			<inputKey name="dm" type="text" description="Data model to
				generate the table for (sdm or sed)">sdm</inputKey>
		</inputTable>"""
    _queriedTable = base.ReferenceAttribute(
        "queriedTable",
        default=base.Undefined,
        description="A reference to the SSAP table"
        " to search the accrefs in",
        copyable=True)
    _sdmDD = base.StructAttribute(
        "sdmDD",
        default=base.Undefined,
        childFactory=rscdef.DataDescriptor,
        description="A data instance that builds the SDM table.  You'll need"
        " a custom or embedded grammar for those that accepts an SDM row"
        " as input.",
        copyable=True)

    def onElementComplete(self):
        warnings.warn(
            "SDMCore is deprecated, and chances are you don't need"
            " need it.  See http://docs.g-vo.org/DaCHS/commonproblems.html for"
            " details.", DeprecationWarning)
        self._onElementCompleteNext(SDMCore)
        if self.sdmDD.getMeta("utype", default=None) is None:
            self.sdmDD.setMeta("utype", "spec:Spectrum")

    def run(self, service, inputTable, queryMeta):
        with base.getTableConn() as conn:
            ssaTable = rsc.TableForDef(self.queriedTable, connection=conn)
            try:
                # XXX TODO: Figure out why the unquote here is required.
                accref = urllib.unquote(inputTable.getParam("accref"))
                res = list(
                    ssaTable.iterQuery(ssaTable.tableDef, "accref=%(accref)s",
                                       {"accref": accref}))
                if not res:
                    raise svcs.UnknownURI(
                        "No spectrum with accref %s known here" %
                        inputTable.getParam("accref"))
                ssaRow = res[0]
            finally:
                ssaTable.close()

        resData = makeSDMDataForSSARow(ssaRow, self.sdmDD)

        votContextArgs = {}
        if queryMeta["tdEnc"]:
            votContextArgs["tablecoding"] = "td"

        # This is for VOSpec, in particular the tablecoding; I guess once
        # we actually support the sed DM, this should go, and the
        # specview links should use sed dcc sourcePaths.
        if inputTable.getParam("dm") == "sed":
            hackSDMToSED(resData)
            votContextArgs["tablecoding"] = "td"

        return (base.votableType,
                votable.asString(makeSDMVOT(resData, **votContextArgs)))
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
示例#6
0
class Make(base.Structure, scripting.ScriptingMixin):
	"""A build recipe for tables belonging to a data descriptor.

	All makes belonging to a DD will be processed in the order in which they
	appear in the file.
	"""
	name_ = "make"

	_table = base.ReferenceAttribute("table", 
		description="Reference to the table to be embedded",
		default=base.Undefined, 
		copyable=True,
		forceType=tabledef.TableDef)

	_rowmaker = base.ReferenceAttribute("rowmaker", 
		default=base.NotGiven,
		forceType=rmkdef.RowmakerDef,
		description="The rowmaker (i.e., mapping rules from grammar keys to"
		" table columns) for the table being made.", 
		copyable=True)

	_parmaker = base.ReferenceAttribute("parmaker", 
		default=base.NotGiven,
		forceType=rmkdef.ParmakerDef,
		description="The parmaker (i.e., mapping rules from grammar parameters"
		" to table parameters) for the table being made.  You will usually"
		" not give a parmaker.",
		copyable=True)

	_role = base.UnicodeAttribute("role", 
		default=None,
		description="The role of the embedded table within the data set",
		copyable=True)
	
	_rowSource = base.EnumeratedUnicodeAttribute("rowSource",
		default="rows",
		validValues=["rows", "parameters"],
		description="Source for the raw rows processed by this rowmaker.",
		copyable=True,
		strip=True)

	def __repr__(self):
		return "Make(table=%r, rowmaker=%r)"%(
			self.table and self.table.id, self.rowmaker and self.rowmaker.id)

	def onParentComplete(self):
		if self.rowmaker is base.NotGiven:
			self.rowmaker = rmkdef.RowmakerDef.makeIdentityFromTable(self.table)

	def getExpander(self):
		"""used by the scripts of expanding their source.

		We always return the expander of the table being made.
		"""
		return self.table.getExpander()
	
	def create(self, connection, parseOptions, tableFactory, **kwargs):
		"""returns a new empty instance of the table this is making.
		"""
		newTable = tableFactory(self.table,
			parseOptions=parseOptions, connection=connection, role=self.role,
			create=True, **kwargs)
		if (self.table.onDisk
				and not parseOptions.updateMode 
				and not getattr(self.parent, "updating", False)):
			newTable._runScripts = self.getRunner()
		return newTable
	
	def runParmakerFor(self, grammarParameters, destTable):
		"""feeds grammarParameter to destTable.
		"""
		if self.parmaker is base.NotGiven:
			return
		parmakerFunc = self.parmaker.compileForTableDef(destTable.tableDef)
		destTable.setParams(parmakerFunc(grammarParameters, destTable),
			raiseOnBadKeys=False)
class Publication(base.Structure, base.ComputedMetaMixin):
    """A specification of how a service should be published.

	This contains most of the metadata for what is an interface in
	registry speak.
	"""
    name_ = "publish"

    _rd = rscdef.RDAttribute()
    _render = base.UnicodeAttribute(
        "render",
        default=base.Undefined,
        description="The renderer the publication will point at.",
        copyable=True)
    _sets = base.StringSetAttribute(
        "sets",
        description="Comma-separated list of sets this service will be"
        " published in.  Predefined are: local=publish on front page,"
        " ivo_managed=register with the VO registry.  If you leave it"
        " empty, 'local' publication is assumed.",
        copyable="True")
    _service = base.ReferenceAttribute(
        "service",
        default=base.NotGiven,
        description="Reference for a service actually implementing the"
        " capability corresponding to this publication.  This is"
        " mainly when there is a vs:WebBrowser service accompanying a VO"
        " protocol service, and this other service should be published"
        " in the same resource record.  See also the operator's guide.",
        copyable="True")
    _auxiliary = base.BooleanAttribute(
        "auxiliary",
        default=False,
        description="Auxiliary publications are for capabilities"
        " not intended to be picked up for all-VO queries, typically"
        " because they are already registered with other services."
        " This is mostly used internally; you probably have no reason"
        " to touch it.")

    def completeElement(self, ctx):
        if self.render is base.Undefined:
            self.render = "form"
        if not self.sets:
            self.sets.add("local")
        if self.service is base.NotGiven:
            self.service = self.parent
        self.setMetaParent(self.service)
        self._completeElementNext(Publication, ctx)

    def validate(self):
        self._validateNext(Publication)
        try:
            renderers.getRenderer(self.render)
        except KeyError:
            raise base.StructureError("Unknown renderer: %s" % self.render)

    def _meta_accessURL(self):
        return self.service.getURL(self.render)

    def _meta_urlUse(self):
        return renderers.getRenderer(self.render).urlUse

    def _meta_requestMethod(self):
        return renderers.getRenderer(self.render).preferredMethod

    def _meta_resultType(self):
        return renderers.getRenderer(self.render).resultType
class ProcApp(ProcDef):
	"""An abstract base for procedure applications.

	Deriving classes need to provide:

		- a requiredType attribute specifying what ProcDefs can be applied.
		- a formalArgs attribute containing a (python) formal argument list
		- of course, a name_ for XML purposes.
	
	They can, in addition, give a class attribute additionalNamesForProcs,
	which is a dictionary that is joined into the global namespace during
	procedure compilation.
	"""
	_procDef = base.ReferenceAttribute("procDef", forceType=ProcDef,
		default=base.NotGiven, description="Reference to the procedure"
		" definition to apply", copyable=True)
	_bindings = base.StructListAttribute("bindings", description=
		"Values for parameters of the procedure definition",
		childFactory=Binding, copyable=True)
	_name = base.UnicodeAttribute("name", default=base.NotGiven,
		description="A name of the proc.  ProcApps compute their (python)"
		" names to be somwhat random strings.  Set a name manually to"
		" receive more easily decipherable error messages.  If you do that,"
		" you have to care about name clashes yourself, though.", strip=True)

	requiredType = None

	additionalNamesForProcs = {}

	def validate(self):
		if self.procDef and self.procDef.type and self.requiredType:
			if self.procDef.type!=self.requiredType:
				raise base.StructureError("The procDef %s has type %s, but"
					" here %s procDefs are required."%(self.procDef.id,
						self.procDef.type, self.requiredType))

		if self.procDef:
			if self.procDef.deprecated:
				if self.getSourcePosition()!="<internally built>":
					# for now, don't warn about these; they typically
					# originate when copying/adapting cores and will just
					# confuse operators
					procId = "unnamed procApp"
					if self.name:
						procId = "procApp %s"%self.name

					base.ui.notifyWarning("%s, %s: %s"%(
						self.getSourcePosition(),
						procId,
						utils.fixIndentation(self.procDef.deprecated, "")))

		self._validateNext(ProcApp)
		self._ensureParsBound()

	def completeElement(self, ctx):
		self._completeElementNext(ProcApp, ctx)
		if self.name is base.NotGiven:  # make up a name from self's id
			self.name = ("proc%x"%id(self)).replace("-", "")

	@utils.memoized
	def getSetupPars(self):
		"""returns the setup parameters for the proc app, where procDef
		parameters may be overridden by self's parameters.
		"""
		allSetups = []
		if self.procDef is not base.NotGiven:
			allSetups.extend(self.procDef.setups)
		allSetups.extend(self.setups)
		return unionByKey(*[s.pars for s in allSetups])

	def _ensureParsBound(self):
		"""raises an error if non-defaulted pars of procDef are not filled
		by the bindings.
		"""
		bindNames = set(b.key for b in self.bindings)
		for p in self.getSetupPars():
			if not p.isDefaulted():
				if not p.key in bindNames:
					raise base.StructureError("Parameter %s is not defaulted in"
						" %s and thus must be bound."%(p.key, self.name))
			if p.key in bindNames:
				bindNames.remove(p.key)

		if bindNames:
			raise base.StructureError("May not bind non-existing parameter(s)"
				" %s."%(", ".join(bindNames)))

	def onElementComplete(self):
		self._onElementCompleteNext(ProcApp)
		self._boundNames = dict((b.key, b.content_) for b in self.bindings)

	def _combineWithProcDef(self, methodName, boundNames):
		# A slightly tricky helper method for the implementation of get*SetupCode:
		# this combines the results of calling methodName on a procDef
		# (where applicable) with calling it on ProcDef for self.
		parts = []
		if self.procDef is not base.NotGiven:
			parts.append(getattr(self.procDef, methodName)(boundNames))

		parts.append(getattr(ProcDef, methodName)(self, boundNames))
		return "\n".join(parts)

	def getLateSetupCode(self, boundNames):
		return self._combineWithProcDef("getLateSetupCode", boundNames)

	def getParSetupCode(self, boundNames):
		return self._combineWithProcDef("getParSetupCode", boundNames)

	def getBodySetupCode(self, boundNames):
		return self._combineWithProcDef("getBodySetupCode", boundNames)

	def getSetupCode(self):
		code = "\n".join((
			self.getParSetupCode(self._boundNames),
			self.getBodySetupCode(self._boundNames)))
		if "\\" in code:
			code = self.parent.expand(code)
		return code

	def _getFunctionDefinition(self, mainSource):
		"""returns mainSource in a function definition with proper 
		signature including setup of late code.
		"""
		parts = [self.getLateSetupCode(self._boundNames)]
		parts.append(mainSource)
		body = "\n".join(parts)
		if not body.strip():
			body = "  pass"
		return "def %s(%s):\n%s"%(self.name, self.formalArgs,
			body)

	def getFuncCode(self):
		"""returns a function definition for this proc application.

		This includes bindings of late parameters.

		Locally defined code overrides code defined in a procDef.
		"""
		mainCode = ""
		if self.code is base.NotGiven:
			if self.procDef is not base.NotGiven:
				mainCode = self.procDef.getCode()
		else:
			mainCode = self.getCode()
		code = self._getFunctionDefinition(mainCode)
		if "\\" in code:
			code = self.parent.expand(code)
		return code

	def _compileForParent(self, parent):
		"""helps compile.
		"""
		# go get the RD for parent; it's always handy in this kind
		# of code
		curEl = parent
		while not hasattr(curEl, "rd"):
			if curEl.parent:
				curEl = curEl.parent
			else:
				break
		try:
			rd = curEl.rd
		except AttributeError:
			# maybe an unrooted element
			rd = None
			
		return rmkfuncs.makeProc(
				self.name, self.getFuncCode(),
				self.getSetupCode(), parent,
				rd=rd,
				**self.additionalNamesForProcs)

	def breakCircles(self):
		# overridden to undo additional memoization
		ProcDef.breakCircles(self)
		utils.forgetMemoized(self)

	def compile(self, parent=None):
		"""returns a callable for this procedure application.

		You can pass a different parent; it will then be used to
		expand macros.  If you do not give it, the embedding structure will
		be used.
		"""
		if parent is None:
			parent = self.parent
		return utils.memoizeOn(parent, self, self._compileForParent, parent)
class ForeignKey(base.Structure):
    """A description of a foreign key relation between this table and another
	one.
	"""
    name_ = "foreignKey"

    _inTable = base.ReferenceAttribute(
        "inTable",
        default=base.Undefined,
        description="Reference to the table the foreign key points to.",
        copyable=True)
    _source = base.UnicodeAttribute(
        "source",
        default=base.Undefined,
        description="Comma-separated list of local columns corresponding"
        " to the foreign key.  No sanity checks are performed here.",
        copyable=True)
    _dest = base.UnicodeAttribute(
        "dest",
        default=base.NotGiven,
        description="Comma-separated list of columns in the target table"
        " belonging to its key.  No checks for their existence, uniqueness,"
        " etc. are done here.  If not given, defaults to source.")
    _metaOnly = base.BooleanAttribute(
        "metaOnly",
        default=False,
        description="Do not tell the database to actually create the foreign"
        " key, just declare it in the metadata.  This is for when you want"
        " to document a relationship but don't want the DB to actually"
        " enforce this.  This is typically a wise thing to do when you have, say"
        " a gigarecord of flux/density pairs and only several thousand metadata"
        " records -- you may want to update the latter without having"
        " to tear down the former.")

    def getDescription(self):
        return "%s:%s -> %s:%s" % (self.parent.getQName(), ",".join(
            self.source), self.destTableName, ".".join(self.dest))

    def _parseList(self, raw):
        if isinstance(raw, list):
            # we're being copied
            return raw
        return [s.strip() for s in raw.split(",") if s.strip()]

    def onElementComplete(self):
        self.destTableName = self.inTable.getQName()
        self.isADQLKey = self.inTable.adql and self.inTable.adql != 'hidden'

        self.source = self._parseList(self.source)
        if self.dest is base.NotGiven:
            self.dest = self.source
        else:
            self.dest = self._parseList(self.dest)
        self._onElementCompleteNext(ForeignKey)

    def create(self, querier):
        if self.metaOnly:
            return

        if not querier.foreignKeyExists(self.parent.getQName(),
                                        self.destTableName, self.source,
                                        self.dest):
            return querier.query(
                "ALTER TABLE %s ADD FOREIGN KEY (%s)"
                " REFERENCES %s (%s)"
                " ON DELETE CASCADE"
                " DEFERRABLE INITIALLY DEFERRED" %
                (self.parent.getQName(), ",".join(
                    self.source), self.destTableName, ",".join(self.dest)))

    def delete(self, querier):
        if self.metaOnly:
            return

        try:
            constraintName = querier.getForeignKeyName(self.parent.getQName(),
                                                       self.destTableName,
                                                       self.source, self.dest)
        except base.DBError:  # key does not exist.
            return
        querier.query("ALTER TABLE %s DROP CONSTRAINT %s" %
                      (self.parent.getQName(), constraintName))

    def getAnnotation(self, roleName, container):
        """returns a dm annotation for this foreign key.
		"""
        return dm.ForeignKeyAnnotation(roleName, self)
示例#10
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
示例#11
0
class TableBasedCore(core.Core):
	"""A core knowing a DB table it operates on and allowing the definition
	of condDescs.
	"""
	_queriedTable = base.ReferenceAttribute("queriedTable",
		default=base.Undefined, description="A reference to the table"
			" this core queries.", copyable=True, callbacks=["_fixNamePath"])
	_condDescs = base.StructListAttribute("condDescs", childFactory=CondDesc,
		description="Descriptions of the SQL and input generating entities"
			" for this core; if not given, they will be generated from the"
			" table columns.", copyable=True)
	_namePath = rscdef.NamePathAttribute(description="Id of an element"
		" that will be used to located names in id references.  Defaults"
		" to the queriedTable's id.")

	def _buildInputTableFromCondDescs(self):
		groups, iks = [], []
		for cd in self.condDescs:
			for ik in cd.inputKeys:
				iks.append(ik)
			if cd.group:
				groups.append(cd.group.change(
					paramRefs=[MS(rscdef.ParameterReference, dest=ik.name)
						for ik in cd.inputKeys], 
					parent_=None))
		self.inputTable = MS(inputdef.InputTable,
			params=iks,
			groups=groups)

	def completeElement(self, ctx):
		# if no condDescs have been given, make them up from the table columns.
		if not self.condDescs and self.queriedTable:
			self.condDescs = [self.adopt(CondDesc.fromColumn(c))
				for c in self.queriedTable]

		# if an inputTable is given, trust it fits the condDescs, else
		# build the input table
		if self.inputTable is base.NotGiven:
			self._buildInputTableFromCondDescs()

		# if no outputTable has been given, make it up from the columns
		# of the queried table unless a prototype is defined (which is
		# handled by core itself).
		if (self.outputTableXML is None 
				and self.outputTable is base.NotGiven
				and self.queriedTable):
			self.outputTable = outputdef.OutputTableDef.fromTableDef(
				self.queriedTable, ctx)

		self._completeElementNext(TableBasedCore, ctx)

	def _fixNamePath(self, qTable):
# callback from queriedTable to make it the default for namePath as well
		if self.namePath is None:
			self.namePath = qTable.getFullId()

	def _getSQLWhere(self, inputTable, queryMeta):
		"""returns a where fragment and the appropriate parameters
		for the query defined by inputTable and queryMeta.
		"""
		sqlPars = {}
		inputPars = dict((p.name, p.value) for p in inputTable.iterParams())
		return base.joinOperatorExpr("AND",
			[cd.asSQL(inputPars, sqlPars, queryMeta)
				for cd in self.condDescs]), sqlPars

	def _makeTable(self, rowIter, resultTableDef, queryMeta):
		"""returns a table from the row iterator rowIter, updating queryMeta
		as necessary.
		"""
		rows = list(rowIter)
		isOverflowed =  len(rows)>queryMeta.get("dbLimit", 1e10)
		if isOverflowed:
			del rows[-1]
		queryMeta["Matched"] = len(rows)
		res = rsc.TableForDef(resultTableDef, rows=rows)
		if isOverflowed:
			queryMeta["Overflow"] = True
			res.addMeta("_warning", "The query limit was reached.  Increase it"
				" to retrieve more matches.  Note that unsorted truncated queries"
				" are not reproducible (i.e., might return a different result set"
				" at a later time).")
			res.addMeta("_queryStatus", "Overflowed")
		else:
			res.addMeta("_queryStatus", "Ok")
		return res
	
	def adaptForRenderer(self, renderer):
		"""returns a core tailored to renderer renderers.

		This mainly means asking the condDescs to build themselves for
		a certain renderer.  If no polymorphuous condDescs are there,
		self is returned.
		"""
		newCondDescs = []
		for cd in self.condDescs:
			newCondDescs.append(cd.adaptForRenderer(renderer))
		if newCondDescs!=self.condDescs:
			return self.change(condDescs=newCondDescs, inputTable=base.NotGiven
				).adaptForRenderer(renderer)
		else:
			return core.Core.adaptForRenderer(self, renderer)
class UploadCore(core.Core):
    """A core handling uploads of files to the database.

	It allows users to upload individual files into a special staging
	area (taken from the stagingDir property of the destination data descriptor)
	and causes these files to be parsed using destDD.

	You can tell UploadCores to either insert or update the incoming data using
	the "mode" input key.
	"""
    name_ = "uploadCore"

    _destDD = base.ReferenceAttribute(
        "destDD",
        default=base.Undefined,
        description="Reference to the data we are uploading into.")

    inputTableXML = """
		<inputTable id="inFields">
			<inputKey name="File" type="file" required="True"
				tablehead="Source to upload"/>
			<inputKey name="Mode" type="text" tablehead="Upload mode" 
				required="True" multiplicity="forced-single">
				<values default="i">
					<option title="Insert">i</option>
					<option title="Update">u</option>
				</values>
			</inputKey>
		</inputTable>
		"""
    outputTableXML = uploadOutputDef

    def _fixPermissions(self, fName):
        """tries to chmod the newly created file to 0664 and change the group
		to config.gavoGroup.
		"""
        try:
            os.chmod(fName, 0664)
            os.chown(fName, -1, grp.getgrnam(base.getConfig("gavoGroup"))[2])
        except (KeyError, os.error):  # let someone else worry about it
            pass

    def _writeFile(self, srcFile, fName):
        """writes the contents of srcFile to fName in destDD's staging dir.
		"""
        try:
            targetDir = os.path.join(self.rd.resdir,
                                     self.destDD.getProperty("stagingDir"))
        except KeyError:
            raise base.ui.logOldExc(
                base.ValidationError(
                    "Uploading is only"
                    " supported for data having a staging directory.", "File"))
        if not os.path.exists(targetDir):
            raise base.ValidationError("Staging directory does not exist.",
                                       "File")
        targetFName = fName.split("/")[-1].encode("iso-8859-1")
        if not targetFName:
            raise base.ValidationError("Bad file name", "File")
        targetPath = os.path.join(targetDir, targetFName)

        with open(targetPath, "w") as f:
            f.write(srcFile.read())

        try:
            self._fixPermissions(targetPath)
        except os.error:
            # Nothing we can do, and it may not even hurt
            pass
        return targetPath

    def _importData(self, sourcePath, mode):
        """parses the input file at sourcePath and writes the result to the DB.
		"""
        base.ui.notifyInfo("Web upload ingesting %s in %s mode" %
                           (sourcePath, mode))
        try:
            parseOptions = rsc.getParseOptions(validateRows=True,
                                               updateMode=True,
                                               doTableUpdates=mode == "u")
            with base.getWritableAdminConn() as conn:
                res = rsc.makeData(self.destDD,
                                   parseOptions=parseOptions,
                                   forceSource=sourcePath,
                                   connection=conn)
        except Exception, msg:
            raise base.ui.logOldExc(
                base.ValidationError(
                    "Cannot enter %s in"
                    " database: %s" % (os.path.basename(sourcePath), str(msg)),
                    "File"))
        return res.nAffected