Пример #1
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 RD(base.Structure, base.ComputedMetaMixin, scripting.ScriptingMixin,
         base.StandardMacroMixin, common.PrivilegesMixin,
         registry.DateUpdatedMixin):
    """A resource descriptor (RD); the root for all elements described here.
	
	RDs collect all information about how to parse a particular source (like a
	collection of FITS images, a catalogue, or whatever), about the database
	tables the data ends up in, and the services used to access them.
	"""
    name_ = "resource"

    # this is set somewhere below once parsing has proceeded far enough
    # such that caching the RD make sense
    cacheable = False

    _resdir = base.FunctionRelativePathAttribute(
        "resdir",
        default=None,
        baseFunction=lambda instance: base.getConfig("inputsDir"),
        description="Base directory for source files and everything else"
        " belonging to the resource.",
        copyable=True)

    _schema = base.UnicodeAttribute(
        "schema",
        default=base.Undefined,
        description="Database schema for tables defined here.  Follow the rule"
        " 'one schema, one RD' if at all possible.  If two RDs share the same"
        " schema, the must generate exactly the same permissions for that"
        " schema; this means, in particular, that if one has an ADQL-published"
        " table, so must the other.  In a nutshell: one schema, one RD.",
        copyable=True,
        callbacks=["_inferResdir"])

    _dds = base.StructListAttribute(
        "dds",
        childFactory=rscdef.DataDescriptor,
        description="Descriptors for the data generated and/or published"
        " within this resource.",
        copyable=True,
        before="outputTables")

    _tables = base.StructListAttribute(
        "tables",
        childFactory=rscdef.TableDef,
        description="A table used or created by this resource",
        copyable=True,
        before="dds")

    _outputTables = base.StructListAttribute(
        "outputTables",
        childFactory=svcs.OutputTableDef,
        description="Canned output tables for later reference.",
        copyable=True)

    _rowmakers = base.StructListAttribute(
        "rowmakers",
        childFactory=rscdef.RowmakerDef,
        description="Transformations for going from grammars to tables."
        " If specified in the RD, they must be referenced from make"
        " elements to become active.",
        copyable=True,
        before="dds")

    _procDefs = base.StructListAttribute(
        "procDefs",
        childFactory=rscdef.ProcDef,
        description="Procedure definintions (rowgens, rowmaker applys)",
        copyable=True,
        before="rowmakers")

    _condDescs = base.StructListAttribute(
        "condDescs",
        childFactory=svcs.CondDesc,
        description="Global condition descriptors for later reference",
        copyable=True,
        before="cores")

    _resRecs = base.StructListAttribute(
        "resRecs",
        childFactory=registry.ResRec,
        description="Non-service resources for the IVOA registry.  They will"
        " be published when gavo publish is run on the RD.")

    _services = base.StructListAttribute(
        "services",
        childFactory=svcs.Service,
        description="Services exposing data from this resource.",
        copyable=True)

    _macDefs = base.MacDefAttribute(
        before="tables",
        description="User-defined macros available on this RD")

    _mixinDefs = base.StructListAttribute(
        "mixdefs",
        childFactory=rscdef.MixinDef,
        description="Mixin definitions (usually not for users)")

    _require = base.ActionAttribute(
        "require",
        methodName="importModule",
        description="Import the named gavo module (for when you need something"
        " registred)")

    _cores = base.MultiStructListAttribute(
        "cores",
        childFactory=svcs.getCore,
        childNames=svcs.CORE_REGISTRY.keys(),
        description="Cores available in this resource.",
        copyable=True,
        before="services")

    _jobs = base.StructListAttribute(
        "jobs",
        childFactory=executing.Execute,
        description="Jobs to be run while this RD is active.")

    _tests = base.StructListAttribute(
        "tests",
        childFactory=regtest.RegTestSuite,
        description="Suites of regression tests connected to this RD.")

    # These replace themselves with expanded tables
    _viewDefs = base.StructAttribute(
        "simpleView",
        childFactory=rscdef.SimpleView,
        description="Definitions of views created from natural joins",
        default=None)

    _properties = base.PropertyAttribute()

    def __init__(self, srcId, **kwargs):
        # RDs never have parents, so contrary to all other structures they
        # are constructed with with a srcId instead of a parent.  You
        # *can* have that None, but such RDs cannot be used to create
        # non-temporary tables, services, etc, since the srcId is used
        # in the construction of identifiers and such.
        self.sourceId = srcId
        base.Structure.__init__(self, None, **kwargs)
        # The rd attribute is a weakref on self.  Always.  So, this is the class
        # that roots common.RDAttributes
        self.rd = weakref.proxy(self)
        # real dateUpdated is set by getRD, this is just for RDs created
        # on the fly.
        self.dateUpdated = datetime.datetime.utcnow()
        # if an RD is parsed from a disk file, this gets set to its path
        # by getRD below
        self.srcPath = None
        # this is for modified-since and friends.
        self.loadedAt = time.time()
        # keep track of RDs depending on us for the registry code
        # (only read this)
        self.rdDependencies = set()

    def __iter__(self):
        return iter(self.dds)

    def __repr__(self):
        return "<resource descriptor for %s>" % self.sourceId

    def validate(self):
        if not utils.identifierPattern.match(self.schema):
            raise base.StructureError("DaCHS schema attributes must be valid"
                                      " python identifiers")

    def isDirty(self):
        """returns true if the RD on disk has a timestamp newer than
		loadedAt.
		"""
        if isinstance(self.srcPath, PkgResourcePath):
            # stuff from the resource package should not change underneath us.
            return False

        try:
            if self.srcPath is not None:
                return os.path.getmtime(self.srcPath) > self.loadedAt
        except os.error:
            # this will ususally mean the file went away
            return True
        return False

    def importModule(self, ctx):
        # this is a callback for the require attribute
        utils.loadInternalObject(self.require, "__doc__")

    def onElementComplete(self):
        for table in self.tables:
            self.readProfiles = self.readProfiles | table.readProfiles
            table.setMetaParent(self)

        self.serviceIndex = {}
        for svc in self.services:
            self.serviceIndex[svc.id] = svc
            svc.setMetaParent(self)

        for dd in self.dds:
            dd.setMetaParent(self)

        if self.resdir and not os.path.isdir(self.resdir):
            base.ui.notifyWarning(
                "RD %s: resource directory '%s' does not exist" %
                (self.sourceId, self.resdir))

        self._onElementCompleteNext(RD)

    def _inferResdir(self, value):
        if self.resdir is None:
            self._resdir.feedObject(self, value)

    def iterDDs(self):
        return iter(self.dds)

    def getService(self, id):
        return self.serviceIndex.get(id, None)

    def getTableDefById(self, id):
        return self.getById(id, rscdef.TableDef)

    def getDataDescById(self, id):
        return self.getById(id, rscdef.DataDescriptor)

    def getById(self, id, forceType=None):
        try:
            res = self.idmap[id]
        except KeyError:
            raise base.NotFoundError(id, "Element with id",
                                     "RD %s" % (self.sourceId))
        if forceType:
            if not isinstance(res, forceType):
                raise base.StructureError("Element with id '%s' is not a %s" %
                                          (id, forceType.__name__))
        return res

    def getAbsPath(self, relPath):
        """returns the absolute path for a resdir-relative relPath.
		"""
        return os.path.join(self.resdir, relPath)

    def openRes(self, relPath, mode="r"):
        """returns a file object for relPath within self's resdir.

		Deprecated.  This is going to go away, use getAbsPath and a context 
		manager.
		"""
        return open(self.getAbsPath(relPath), mode)

    def getTimestampPath(self):
        """returns a path to a file that's accessed by Resource each time 
		a bit of the described resource is written to the db.
		"""
        return os.path.join(base.getConfig("stateDir"),
                            "updated_" + self.sourceId.replace("/", "+"))

    def touchTimestamp(self):
        """updates the timestamp on the rd's state file.
		"""
        fn = self.getTimestampPath()
        try:
            try:
                os.unlink(fn)
            except os.error:
                pass
            f = open(fn, "w")
            f.close()
            os.chmod(fn, 0664)
            try:
                os.chown(fn, -1, grp.getgrnam(base.getConfig("GavoGroup")[2]))
            except (KeyError, os.error):
                pass
        except (os.error, IOError):
            base.ui.notifyWarning("Could not update timestamp on RD %s" %
                                  self.sourceId)

    def _computeIdmap(self):
        res = {}
        for child in self.iterChildren():
            if hasattr(child, "id"):
                res[child.id] = child
        return res

    def addDependency(self, rd, prereq):
        """declares that rd needs the RD prereq to properly work.

		This is used in the generation of resource records to ensure that, e.g.
		registred data have added their served-bys to the service resources.
		"""
        if rd.sourceId != prereq.sourceId:
            self.rdDependencies.add((rd.sourceId, prereq.sourceId))

    def copy(self, parent):
        base.ui.notifyWarning("Copying an RD -- this may not be a good idea")
        new = base.Structure.copy(self, parent)
        new.idmap = new._computeIdmap()
        new.sourceId = self.sourceId
        return new

    def invalidate(self):
        """make the RD fail on every attribute read.

		See rscdesc._loadRDIntoCache for why we want this.
		"""
        errMsg = ("Loading of %s failed in another thread; this RD cannot"
                  " be used here") % self.sourceId

        class BrokenClass(object):
            """A class that reacts to all attribute requests with a some exception.
			"""
            def __getattribute__(self, attributeName):
                if attributeName == "__class__":
                    return BrokenClass
                raise base.ReportableError(errMsg)

        self.__class__ = BrokenClass

    def macro_RSTccby(self, stuffDesignation):
        """expands to a declaration that stuffDesignation is available under
		CC-BY.
		
		This only works in reStructured text (though it's still almost
		readable as source).
		"""
        return ("%s is licensed under the `Creative Commons Attribution 3.0"
                " License <http://creativecommons.org/licenses/by/3.0/>`_\n\n"
                ".. image:: /static/img/ccby.png\n\n") % stuffDesignation

    def macro_RSTcc0(self, stuffDesignation):
        """expands to a declaration that stuffDesignation is available under
		CC-0.
		
		This only works in reStructured text (though it's still almost
		readable as source).
		"""
        return (
            "To the extent possible under law, the publisher has"
            " waived all copyright and related or neighboring rights to %s."
            "  For details, see the `Creative Commons CC0 1.0"
            " Public Domain dedication"
            " <http://creativecommons.org/publicdomain/zero/1.0/>`_.  Of course,"
            " you should still give proper credit when using this data as"
            " required by good scientific practice.\n\n"
            ".. image:: /static/img/cc0.png\n\n") % stuffDesignation