class Collection(dict): """A collection of handles. Runtime representation of a collection's metadata. This is independent on its physical representation and therefore uses any CollectionBackend to set and/or retrieve the data. A Collection is a dictionary, which's keys are the handles' names. The values are Handle instances representing the metadata of these handles. Additionally, a Collection has attributes to store data about the collection itself: Attributes of a collection: name: str store: IOMemory meta: (named) Graph conjunctive_graph: ConjunctiveGraph To represent the metadata, Collections use a named graph per handle and an additional named graph for collection level metadata. These graphs can be queried via the collection's graph store and its corresponding conjunctive graph. """ def __init__(self, src=None, name=None): # TODO: What about the 'name' option? How to treat it, in case src # provides a name already? For now use it only if src==None. # type(src) == Collection => copy has its own 'name'? # type(src) == Backend => rename in backend? super(Collection, self).__init__() if isinstance(src, Collection): self._backend = None # TODO: confirm this is correct behaviour and document it. # Means, it is a pure runtime copy with no persistence and no # update from backend. self.update(src) self.store = IOMemory() for graph in src.store.contexts(): self.store.add_graph(graph) if graph.identifier == Literal(src.name): self.meta = graph else: self[str(graph.identifier)].meta = graph self.conjunctive_graph = ConjunctiveGraph(store=self.store) elif isinstance(src, CollectionBackend): self._backend = src self.store = None # TODO: check for existence in reload() fails otherwise; # If it turns out, that reload is never required outside of # constructor, that check isn't needed! self._reload() elif src is None: self._backend = None self.store = IOMemory() self.meta = Graph(store=self.store, identifier=Literal(name)) self.meta.add((DLNS.this, RDF.type, DLNS.Collection)) self.conjunctive_graph = ConjunctiveGraph(store=self.store) else: lgr.error("Unknown source for Collection(): %s" % type(src)) raise TypeError('Unknown source for Collection(): %s' % type(src)) @property def name(self): return str(self.meta.identifier) @property def url(self): return self._backend.url def __delitem__(self, key): lgr.error("__delitem__ called.") self_uri = self.meta.value(predicate=RDF.type, object=DLNS.Collection) key_uri = self[key].meta.value(predicate=RDF.type, object=DLNS.Handle) self.meta.remove((self_uri, DCTERMS.hasPart, key_uri)) self.store.remove_graph(self[key].name) super(Collection, self).__delitem__(key) def __setitem__(self, key, value): if not isinstance(value, Handle): raise TypeError("Can't add non-Handle object to a collection.") super(Collection, self).__setitem__(key, value) self_uri = self.meta.value(predicate=RDF.type, object=DLNS.Collection) key_uri = self[key].meta.value(predicate=RDF.type, object=DLNS.Handle) self.meta.add((self_uri, DCTERMS.hasPart, key_uri)) self.store.add_graph(self[key].meta) def _reload(self): # TODO: When do we need to reload outside of the constructor? # May be override self.update() to additionally reload in case # there is a backend. if not self._backend: # TODO: Error or warning? Depends on when we want to call this one. # By now this should be an error (or even an exception). lgr.error("Missing collection backend.") return # get the handles as instances of class Handle: self.update(self._backend.get_handles()) # get collection level data: collection_data = self._backend.get_collection() # TODO: May be a backend can just pass a newly created store containing # all the needed graphs. Would save us time and space for copy, but # seems to be less flexible in case we find another way to store a set # of named graphs and their conjunctive graph without the need of every # collection to have its own store. # Note: By using store.add() there seems to be no copy at all. # Need to check in detail, how this is stored and whether it still # works as intended. # Note 2: Definitely not a copy and seems to work. Need more queries to # check. # cleanup old store, if exists if self.store is not None: self.store.gc() del self.store gc.collect() # create new store for the graphs: self.store = IOMemory() # add collection's own graph: self.store.add_graph(collection_data) self.meta = collection_data # add handles' graphs: for handle in self: self.store.add_graph(self[handle].meta) # reference to the conjunctive graph to be queried: self.conjunctive_graph = ConjunctiveGraph(store=self.store) def query(self): # Note: As long as we use general SPARQL-Queries, no method is needed, # since this is a method of rdflib.Graph/rdflib.Store. # But we will need some kind of prepared queries here. # Also depends on the implementation of the 'ontology translation layer' pass def commit(self, msg="Collection updated."): if not self._backend: lgr.error("Missing collection backend.") raise RuntimeError("Missing collection backend.") self._backend.commit_collection(self, msg)