Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/rdflib/graph.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.7/site-packages/rdflib/graph.py Sat May 02 07:14:21 2020 -0400 @@ -0,0 +1,1954 @@ +from rdflib.term import Literal # required for doctests +assert Literal # avoid warning +from rdflib.namespace import Namespace # required for doctests +assert Namespace # avoid warning +from rdflib.py3compat import format_doctest_out + +__doc__ = format_doctest_out("""\ + +RDFLib defines the following kinds of Graphs: + +* :class:`~rdflib.graph.Graph` +* :class:`~rdflib.graph.QuotedGraph` +* :class:`~rdflib.graph.ConjunctiveGraph` +* :class:`~rdflib.graph.Dataset` + +Graph +----- + +An RDF graph is a set of RDF triples. Graphs support the python ``in`` +operator, as well as iteration and some operations like union, +difference and intersection. + +see :class:`~rdflib.graph.Graph` + +Conjunctive Graph +----------------- + +A Conjunctive Graph is the most relevant collection of graphs that are +considered to be the boundary for closed world assumptions. This +boundary is equivalent to that of the store instance (which is itself +uniquely identified and distinct from other instances of +:class:`Store` that signify other Conjunctive Graphs). It is +equivalent to all the named graphs within it and associated with a +``_default_`` graph which is automatically assigned a :class:`BNode` +for an identifier - if one isn't given. + +see :class:`~rdflib.graph.ConjunctiveGraph` + +Quoted graph +------------ + +The notion of an RDF graph [14] is extended to include the concept of +a formula node. A formula node may occur wherever any other kind of +node can appear. Associated with a formula node is an RDF graph that +is completely disjoint from all other graphs; i.e. has no nodes in +common with any other graph. (It may contain the same labels as other +RDF graphs; because this is, by definition, a separate graph, +considerations of tidiness do not apply between the graph at a formula +node and any other graph.) + +This is intended to map the idea of "{ N3-expression }" that is used +by N3 into an RDF graph upon which RDF semantics is defined. + +see :class:`~rdflib.graph.QuotedGraph` + +Dataset +------- + +The RDF 1.1 Dataset, a small extension to the Conjunctive Graph. The +primary term is "graphs in the datasets" and not "contexts with quads" +so there is a separate method to set/retrieve a graph in a dataset and +to operate with dataset graphs. As a consequence of this approach, +dataset graphs cannot be identified with blank nodes, a name is always +required (RDFLib will automatically add a name if one is not provided +at creation time). This implementation includes a convenience method +to directly add a single quad to a dataset graph. + +see :class:`~rdflib.graph.Dataset` + +Working with graphs +=================== + +Instantiating Graphs with default store (IOMemory) and default identifier +(a BNode): + + >>> g = Graph() + >>> g.store.__class__ + <class 'rdflib.plugins.memory.IOMemory'> + >>> g.identifier.__class__ + <class 'rdflib.term.BNode'> + +Instantiating Graphs with a IOMemory store and an identifier - +<http://rdflib.net>: + + >>> g = Graph('IOMemory', URIRef("http://rdflib.net")) + >>> g.identifier + rdflib.term.URIRef(%(u)s'http://rdflib.net') + >>> str(g) # doctest: +NORMALIZE_WHITESPACE + "<http://rdflib.net> a rdfg:Graph;rdflib:storage + [a rdflib:Store;rdfs:label 'IOMemory']." + +Creating a ConjunctiveGraph - The top level container for all named Graphs +in a 'database': + + >>> g = ConjunctiveGraph() + >>> str(g.default_context) + "[a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'IOMemory']]." + +Adding / removing reified triples to Graph and iterating over it directly or +via triple pattern: + + >>> g = Graph() + >>> statementId = BNode() + >>> print(len(g)) + 0 + >>> g.add((statementId, RDF.type, RDF.Statement)) + >>> g.add((statementId, RDF.subject, + ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph'))) + >>> g.add((statementId, RDF.predicate, RDFS.label)) + >>> g.add((statementId, RDF.object, Literal("Conjunctive Graph"))) + >>> print(len(g)) + 4 + >>> for s, p, o in g: + ... print(type(s)) + ... + <class 'rdflib.term.BNode'> + <class 'rdflib.term.BNode'> + <class 'rdflib.term.BNode'> + <class 'rdflib.term.BNode'> + + >>> for s, p, o in g.triples((None, RDF.object, None)): + ... print(o) + ... + Conjunctive Graph + >>> g.remove((statementId, RDF.type, RDF.Statement)) + >>> print(len(g)) + 3 + +``None`` terms in calls to :meth:`~rdflib.graph.Graph.triples` can be +thought of as "open variables". + +Graph support set-theoretic operators, you can add/subtract graphs, as +well as intersection (with multiplication operator g1*g2) and xor (g1 +^ g2). + +Note that BNode IDs are kept when doing set-theoretic operations, this +may or may not be what you want. Two named graphs within the same +application probably want share BNode IDs, two graphs with data from +different sources probably not. If your BNode IDs are all generated +by RDFLib they are UUIDs and unique. + + >>> g1 = Graph() + >>> g2 = Graph() + >>> u = URIRef(%(u)s'http://example.com/foo') + >>> g1.add([u, RDFS.label, Literal('foo')]) + >>> g1.add([u, RDFS.label, Literal('bar')]) + >>> g2.add([u, RDFS.label, Literal('foo')]) + >>> g2.add([u, RDFS.label, Literal('bing')]) + >>> len(g1 + g2) # adds bing as label + 3 + >>> len(g1 - g2) # removes foo + 1 + >>> len(g1 * g2) # only foo + 1 + >>> g1 += g2 # now g1 contains everything + + +Graph Aggregation - ConjunctiveGraphs and ReadOnlyGraphAggregate within +the same store: + + >>> store = plugin.get('IOMemory', Store)() + >>> g1 = Graph(store) + >>> g2 = Graph(store) + >>> g3 = Graph(store) + >>> stmt1 = BNode() + >>> stmt2 = BNode() + >>> stmt3 = BNode() + >>> g1.add((stmt1, RDF.type, RDF.Statement)) + >>> g1.add((stmt1, RDF.subject, + ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph'))) + >>> g1.add((stmt1, RDF.predicate, RDFS.label)) + >>> g1.add((stmt1, RDF.object, Literal("Conjunctive Graph"))) + >>> g2.add((stmt2, RDF.type, RDF.Statement)) + >>> g2.add((stmt2, RDF.subject, + ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph'))) + >>> g2.add((stmt2, RDF.predicate, RDF.type)) + >>> g2.add((stmt2, RDF.object, RDFS.Class)) + >>> g3.add((stmt3, RDF.type, RDF.Statement)) + >>> g3.add((stmt3, RDF.subject, + ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph'))) + >>> g3.add((stmt3, RDF.predicate, RDFS.comment)) + >>> g3.add((stmt3, RDF.object, Literal( + ... "The top-level aggregate graph - The sum " + + ... "of all named graphs within a Store"))) + >>> len(list(ConjunctiveGraph(store).subjects(RDF.type, RDF.Statement))) + 3 + >>> len(list(ReadOnlyGraphAggregate([g1,g2]).subjects( + ... RDF.type, RDF.Statement))) + 2 + +ConjunctiveGraphs have a :meth:`~rdflib.graph.ConjunctiveGraph.quads` method +which returns quads instead of triples, where the fourth item is the Graph +(or subclass thereof) instance in which the triple was asserted: + + >>> uniqueGraphNames = set( + ... [graph.identifier for s, p, o, graph in ConjunctiveGraph(store + ... ).quads((None, RDF.predicate, None))]) + >>> len(uniqueGraphNames) + 3 + >>> unionGraph = ReadOnlyGraphAggregate([g1, g2]) + >>> uniqueGraphNames = set( + ... [graph.identifier for s, p, o, graph in unionGraph.quads( + ... (None, RDF.predicate, None))]) + >>> len(uniqueGraphNames) + 2 + +Parsing N3 from a string + + >>> g2 = Graph() + >>> src = ''' + ... @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . + ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + ... [ a rdf:Statement ; + ... rdf:subject <http://rdflib.net/store#ConjunctiveGraph>; + ... rdf:predicate rdfs:label; + ... rdf:object "Conjunctive Graph" ] . + ... ''' + >>> g2 = g2.parse(data=src, format='n3') + >>> print(len(g2)) + 4 + +Using Namespace class: + + >>> RDFLib = Namespace('http://rdflib.net/') + >>> RDFLib.ConjunctiveGraph + rdflib.term.URIRef(%(u)s'http://rdflib.net/ConjunctiveGraph') + >>> RDFLib['Graph'] + rdflib.term.URIRef(%(u)s'http://rdflib.net/Graph') + +""") + +import logging +logger = logging.getLogger(__name__) + +# import md5 +import random +import warnings + +from hashlib import md5 + +try: + from io import BytesIO + assert BytesIO +except ImportError: + try: + from io import StringIO as BytesIO + assert BytesIO + except ImportError: + from io import StringIO as BytesIO + assert BytesIO + +from rdflib.namespace import RDF, RDFS, SKOS + +from rdflib import plugin, exceptions, query + +from rdflib.term import Node, URIRef, Genid +from rdflib.term import BNode + +import rdflib.term + +from rdflib.paths import Path + +from rdflib.store import Store +from rdflib.serializer import Serializer +from rdflib.parser import Parser +from rdflib.parser import create_input_source +from rdflib.namespace import NamespaceManager +from rdflib.resource import Resource +from rdflib.collection import Collection +from rdflib import py3compat +b = py3compat.b + +import os +import shutil +import tempfile +from urllib.parse import urlparse + +__all__ = [ + 'Graph', 'ConjunctiveGraph', 'QuotedGraph', 'Seq', + 'ModificationException', 'Dataset', + 'UnSupportedAggregateOperation', 'ReadOnlyGraphAggregate'] + + +class Graph(Node): + """An RDF Graph + + The constructor accepts one argument, the 'store' + that will be used to store the graph data (see the 'store' + package for stores currently shipped with rdflib). + + Stores can be context-aware or unaware. Unaware stores take up + (some) less space but cannot support features that require + context, such as true merging/demerging of sub-graphs and + provenance. + + The Graph constructor can take an identifier which identifies the Graph + by name. If none is given, the graph is assigned a BNode for its + identifier. + For more on named graphs, see: http://www.w3.org/2004/03/trix/ + + """ + + def __init__(self, store='default', identifier=None, + namespace_manager=None): + super(Graph, self).__init__() + self.__identifier = identifier or BNode() + + if not isinstance(self.__identifier, Node): + self.__identifier = URIRef(self.__identifier) + + if not isinstance(store, Store): + # TODO: error handling + self.__store = store = plugin.get(store, Store)() + else: + self.__store = store + self.__namespace_manager = namespace_manager + self.context_aware = False + self.formula_aware = False + self.default_union = False + + def __get_store(self): + return self.__store + store = property(__get_store) # read-only attr + + def __get_identifier(self): + return self.__identifier + identifier = property(__get_identifier) # read-only attr + + def _get_namespace_manager(self): + if self.__namespace_manager is None: + self.__namespace_manager = NamespaceManager(self) + return self.__namespace_manager + + def _set_namespace_manager(self, nm): + self.__namespace_manager = nm + + namespace_manager = property(_get_namespace_manager, + _set_namespace_manager, + doc="this graph's namespace-manager") + + def __repr__(self): + return "<Graph identifier=%s (%s)>" % (self.identifier, type(self)) + + def __str__(self): + if isinstance(self.identifier, URIRef): + return ("%s a rdfg:Graph;rdflib:storage " + + "[a rdflib:Store;rdfs:label '%s'].") % ( + self.identifier.n3(), + self.store.__class__.__name__) + else: + return ("[a rdfg:Graph;rdflib:storage " + + "[a rdflib:Store;rdfs:label '%s']].") % ( + self.store.__class__.__name__) + + def toPython(self): + return self + + def destroy(self, configuration): + """Destroy the store identified by `configuration` if supported""" + self.__store.destroy(configuration) + + # Transactional interfaces (optional) + def commit(self): + """Commits active transactions""" + self.__store.commit() + + def rollback(self): + """Rollback active transactions""" + self.__store.rollback() + + def open(self, configuration, create=False): + """Open the graph store + + Might be necessary for stores that require opening a connection to a + database or acquiring some resource. + """ + return self.__store.open(configuration, create) + + def close(self, commit_pending_transaction=False): + """Close the graph store + + Might be necessary for stores that require closing a connection to a + database or releasing some resource. + """ + self.__store.close( + commit_pending_transaction=commit_pending_transaction) + + def add(self, xxx_todo_changeme): + """Add a triple with self as context""" + (s, p, o) = xxx_todo_changeme + assert isinstance(s, Node), \ + "Subject %s must be an rdflib term" % (s,) + assert isinstance(p, Node), \ + "Predicate %s must be an rdflib term" % (p,) + assert isinstance(o, Node), \ + "Object %s must be an rdflib term" % (o,) + self.__store.add((s, p, o), self, quoted=False) + + def addN(self, quads): + """Add a sequence of triple with context""" + + self.__store.addN((s, p, o, c) for s, p, o, c in quads + if isinstance(c, Graph) + and c.identifier is self.identifier + and _assertnode(s,p,o) + ) + + def remove(self, xxx_todo_changeme1): + """Remove a triple from the graph + + If the triple does not provide a context attribute, removes the triple + from all contexts. + """ + (s, p, o) = xxx_todo_changeme1 + self.__store.remove((s, p, o), context=self) + + def triples(self, xxx_todo_changeme2): + """Generator over the triple store + + Returns triples that match the given triple pattern. If triple pattern + does not provide a context, all contexts will be searched. + """ + (s, p, o) = xxx_todo_changeme2 + if isinstance(p, Path): + for _s, _o in p.eval(self, s, o): + yield (_s, p, _o) + else: + for (s, p, o), cg in self.__store.triples((s, p, o), context=self): + yield (s, p, o) + + @py3compat.format_doctest_out + def __getitem__(self, item): + """ + A graph can be "sliced" as a shortcut for the triples method + The python slice syntax is (ab)used for specifying triples. + A generator over matches is returned, + the returned tuples include only the parts not given + + >>> import rdflib + >>> g = rdflib.Graph() + >>> g.add((rdflib.URIRef('urn:bob'), rdflib.RDFS.label, rdflib.Literal('Bob'))) + + >>> list(g[rdflib.URIRef('urn:bob')]) # all triples about bob + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), rdflib.term.Literal(%(u)s'Bob'))] + + >>> list(g[:rdflib.RDFS.label]) # all label triples + [(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.Literal(%(u)s'Bob'))] + + >>> list(g[::rdflib.Literal('Bob')]) # all triples with bob as object + [(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'))] + + Combined with SPARQL paths, more complex queries can be + written concisely: + + Name of all Bobs friends: + + g[bob : FOAF.knows/FOAF.name ] + + Some label for Bob: + + g[bob : DC.title|FOAF.name|RDFS.label] + + All friends and friends of friends of Bob + + g[bob : FOAF.knows * '+'] + + etc. + + .. versionadded:: 4.0 + + """ + + if isinstance(item, slice): + + s,p,o=item.start,item.stop,item.step + if s is None and p is None and o is None: + return self.triples((s,p,o)) + elif s is None and p is None: + return self.subject_predicates(o) + elif s is None and o is None: + return self.subject_objects(p) + elif p is None and o is None: + return self.predicate_objects(s) + elif s is None: + return self.subjects(p,o) + elif p is None: + return self.predicates(s,o) + elif o is None: + return self.objects(s,p) + else: + # all given + return (s,p,o) in self + + elif isinstance(item, (Path,Node)): + + return self.predicate_objects(item) + + else: + raise TypeError("You can only index a graph by a single rdflib term or path, or a slice of rdflib terms.") + + def __len__(self): + """Returns the number of triples in the graph + + If context is specified then the number of triples in the context is + returned instead. + """ + return self.__store.__len__(context=self) + + def __iter__(self): + """Iterates over all triples in the store""" + return self.triples((None, None, None)) + + def __contains__(self, triple): + """Support for 'triple in graph' syntax""" + for triple in self.triples(triple): + return True + return False + + def __hash__(self): + return hash(self.identifier) + + def md5_term_hash(self): + d = md5(str(self.identifier)) + d.update("G") + return d.hexdigest() + + def __cmp__(self, other): + if other is None: + return -1 + elif isinstance(other, Graph): + return cmp(self.identifier, other.identifier) + else: + # Note if None is considered equivalent to owl:Nothing + # Then perhaps a graph with length 0 should be considered + # equivalent to None (if compared to it)? + return 1 + + def __eq__(self, other): + return isinstance(other, Graph) \ + and self.identifier == other.identifier + + def __lt__(self, other): + return (other is None) \ + or (isinstance(other, Graph) + and self.identifier < other.identifier) + + def __le__(self, other): + return self < other or self == other + + def __gt__(self, other): + return (isinstance(other, Graph) + and self.identifier > other.identifier) \ + or (other is not None) + + def __ge__(self, other): + return self > other or self == other + + def __iadd__(self, other): + """Add all triples in Graph other to Graph. + BNode IDs are not changed.""" + self.addN((s, p, o, self) for s, p, o in other) + return self + + def __isub__(self, other): + """Subtract all triples in Graph other from Graph. + BNode IDs are not changed.""" + for triple in other: + self.remove(triple) + return self + + def __add__(self, other): + """Set-theoretic union + BNode IDs are not changed.""" + retval = Graph() + for (prefix, uri) in set( + list(self.namespaces()) + list(other.namespaces())): + retval.bind(prefix, uri) + for x in self: + retval.add(x) + for y in other: + retval.add(y) + return retval + + def __mul__(self, other): + """Set-theoretic intersection. + BNode IDs are not changed.""" + retval = Graph() + for x in other: + if x in self: + retval.add(x) + return retval + + def __sub__(self, other): + """Set-theoretic difference. + BNode IDs are not changed.""" + retval = Graph() + for x in self: + if not x in other: + retval.add(x) + return retval + + def __xor__(self, other): + """Set-theoretic XOR. + BNode IDs are not changed.""" + return (self - other) + (other - self) + + __or__ = __add__ + __and__ = __mul__ + + # Conv. methods + + def set(self, triple): + """Convenience method to update the value of object + + Remove any existing triples for subject and predicate before adding + (subject, predicate, object). + """ + (subject, predicate, object_) = triple + assert subject is not None, \ + "s can't be None in .set([s,p,o]), as it would remove (*, p, *)" + assert predicate is not None, \ + "p can't be None in .set([s,p,o]), as it would remove (s, *, *)" + self.remove((subject, predicate, None)) + self.add((subject, predicate, object_)) + + def subjects(self, predicate=None, object=None): + """A generator of subjects with the given predicate and object""" + for s, p, o in self.triples((None, predicate, object)): + yield s + + def predicates(self, subject=None, object=None): + """A generator of predicates with the given subject and object""" + for s, p, o in self.triples((subject, None, object)): + yield p + + def objects(self, subject=None, predicate=None): + """A generator of objects with the given subject and predicate""" + for s, p, o in self.triples((subject, predicate, None)): + yield o + + def subject_predicates(self, object=None): + """A generator of (subject, predicate) tuples for the given object""" + for s, p, o in self.triples((None, None, object)): + yield s, p + + def subject_objects(self, predicate=None): + """A generator of (subject, object) tuples for the given predicate""" + for s, p, o in self.triples((None, predicate, None)): + yield s, o + + def predicate_objects(self, subject=None): + """A generator of (predicate, object) tuples for the given subject""" + for s, p, o in self.triples((subject, None, None)): + yield p, o + + def triples_choices(self, xxx_todo_changeme3, context=None): + (subject, predicate, object_) = xxx_todo_changeme3 + for (s, p, o), cg in self.store.triples_choices( + (subject, predicate, object_), context=self): + yield (s, p, o) + + def value(self, subject=None, predicate=RDF.value, object=None, + default=None, any=True): + """Get a value for a pair of two criteria + + Exactly one of subject, predicate, object must be None. Useful if one + knows that there may only be one value. + + It is one of those situations that occur a lot, hence this + 'macro' like utility + + Parameters: + subject, predicate, object -- exactly one must be None + default -- value to be returned if no values found + any -- if True, return any value in the case there is more than one, + else, raise UniquenessError + """ + retval = default + + if (subject is None and predicate is None) or \ + (subject is None and object is None) or \ + (predicate is None and object is None): + return None + + if object is None: + values = self.objects(subject, predicate) + if subject is None: + values = self.subjects(predicate, object) + if predicate is None: + values = self.predicates(subject, object) + + try: + retval = next(values) + except StopIteration: + retval = default + else: + if any is False: + try: + next(values) + msg = ("While trying to find a value for (%s, %s, %s) the" + " following multiple values where found:\n" % + (subject, predicate, object)) + triples = self.store.triples( + (subject, predicate, object), None) + for (s, p, o), contexts in triples: + msg += "(%s, %s, %s)\n (contexts: %s)\n" % ( + s, p, o, list(contexts)) + raise exceptions.UniquenessError(msg) + except StopIteration: + pass + return retval + + def label(self, subject, default=''): + """Query for the RDFS.label of the subject + + Return default if no label exists or any label if multiple exist. + """ + if subject is None: + return default + return self.value(subject, RDFS.label, default=default, any=True) + + @py3compat.format_doctest_out + def preferredLabel(self, subject, lang=None, default=None, + labelProperties=(SKOS.prefLabel, RDFS.label)): + """ + Find the preferred label for subject. + + By default prefers skos:prefLabels over rdfs:labels. In case at least + one prefLabel is found returns those, else returns labels. In case a + language string (e.g., 'en', 'de' or even '' for no lang-tagged + literals) is given, only such labels will be considered. + + Return a list of (labelProp, label) pairs, where labelProp is either + skos:prefLabel or rdfs:label. + + >>> from rdflib import ConjunctiveGraph, URIRef, RDFS, Literal + >>> from rdflib.namespace import SKOS + >>> from pprint import pprint + >>> g = ConjunctiveGraph() + >>> u = URIRef(%(u)s'http://example.com/foo') + >>> g.add([u, RDFS.label, Literal('foo')]) + >>> g.add([u, RDFS.label, Literal('bar')]) + >>> pprint(sorted(g.preferredLabel(u))) + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), + rdflib.term.Literal(%(u)s'bar')), + (rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), + rdflib.term.Literal(%(u)s'foo'))] + >>> g.add([u, SKOS.prefLabel, Literal('bla')]) + >>> pprint(g.preferredLabel(u)) + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'), + rdflib.term.Literal(%(u)s'bla'))] + >>> g.add([u, SKOS.prefLabel, Literal('blubb', lang='en')]) + >>> sorted(g.preferredLabel(u)) #doctest: +NORMALIZE_WHITESPACE + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'), + rdflib.term.Literal(%(u)s'bla')), + (rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'), + rdflib.term.Literal(%(u)s'blubb', lang='en'))] + >>> g.preferredLabel(u, lang='') #doctest: +NORMALIZE_WHITESPACE + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'), + rdflib.term.Literal(%(u)s'bla'))] + >>> pprint(g.preferredLabel(u, lang='en')) + [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'), + rdflib.term.Literal(%(u)s'blubb', lang='en'))] + """ + + if default is None: + default = [] + + # setup the language filtering + if lang is not None: + if lang == '': # we only want not language-tagged literals + langfilter = lambda l: l.language is None + else: + langfilter = lambda l: l.language == lang + else: # we don't care about language tags + langfilter = lambda l: True + + for labelProp in labelProperties: + labels = list(filter(langfilter, self.objects(subject, labelProp))) + if len(labels) == 0: + continue + else: + return [(labelProp, l) for l in labels] + return default + + def comment(self, subject, default=''): + """Query for the RDFS.comment of the subject + + Return default if no comment exists + """ + if subject is None: + return default + return self.value(subject, RDFS.comment, default=default, any=True) + + def items(self, list): + """Generator over all items in the resource specified by list + + list is an RDF collection. + """ + chain = set([list]) + while list: + item = self.value(list, RDF.first) + if item is not None: + yield item + list = self.value(list, RDF.rest) + if list in chain: + raise ValueError("List contains a recursive rdf:rest reference") + chain.add(list) + + def transitiveClosure(self, func, arg, seen=None): + """ + Generates transitive closure of a user-defined + function against the graph + + >>> from rdflib.collection import Collection + >>> g=Graph() + >>> a=BNode('foo') + >>> b=BNode('bar') + >>> c=BNode('baz') + >>> g.add((a,RDF.first,RDF.type)) + >>> g.add((a,RDF.rest,b)) + >>> g.add((b,RDF.first,RDFS.label)) + >>> g.add((b,RDF.rest,c)) + >>> g.add((c,RDF.first,RDFS.comment)) + >>> g.add((c,RDF.rest,RDF.nil)) + >>> def topList(node,g): + ... for s in g.subjects(RDF.rest,node): + ... yield s + >>> def reverseList(node,g): + ... for f in g.objects(node,RDF.first): + ... print(f) + ... for s in g.subjects(RDF.rest,node): + ... yield s + + >>> [rt for rt in g.transitiveClosure( + ... topList,RDF.nil)] # doctest: +NORMALIZE_WHITESPACE + [rdflib.term.BNode('baz'), + rdflib.term.BNode('bar'), + rdflib.term.BNode('foo')] + + >>> [rt for rt in g.transitiveClosure( + ... reverseList,RDF.nil)] # doctest: +NORMALIZE_WHITESPACE + http://www.w3.org/2000/01/rdf-schema#comment + http://www.w3.org/2000/01/rdf-schema#label + http://www.w3.org/1999/02/22-rdf-syntax-ns#type + [rdflib.term.BNode('baz'), + rdflib.term.BNode('bar'), + rdflib.term.BNode('foo')] + + """ + if seen is None: + seen = {} + elif arg in seen: + return + seen[arg] = 1 + for rt in func(arg, self): + yield rt + for rt_2 in self.transitiveClosure(func, rt, seen): + yield rt_2 + + def transitive_objects(self, subject, property, remember=None): + """Transitively generate objects for the ``property`` relationship + + Generated objects belong to the depth first transitive closure of the + ``property`` relationship starting at ``subject``. + """ + if remember is None: + remember = {} + if subject in remember: + return + remember[subject] = 1 + yield subject + for object in self.objects(subject, property): + for o in self.transitive_objects(object, property, remember): + yield o + + def transitive_subjects(self, predicate, object, remember=None): + """Transitively generate objects for the ``property`` relationship + + Generated objects belong to the depth first transitive closure of the + ``property`` relationship starting at ``subject``. + """ + if remember is None: + remember = {} + if object in remember: + return + remember[object] = 1 + yield object + for subject in self.subjects(predicate, object): + for s in self.transitive_subjects(predicate, subject, remember): + yield s + + def seq(self, subject): + """Check if subject is an rdf:Seq + + If yes, it returns a Seq class instance, None otherwise. + """ + if (subject, RDF.type, RDF.Seq) in self: + return Seq(self, subject) + else: + return None + + def qname(self, uri): + return self.namespace_manager.qname(uri) + + def compute_qname(self, uri, generate=True): + return self.namespace_manager.compute_qname(uri, generate) + + def bind(self, prefix, namespace, override=True): + """Bind prefix to namespace + + If override is True will bind namespace to given prefix even + if namespace was already bound to a different prefix. + + for example: graph.bind('foaf', 'http://xmlns.com/foaf/0.1/') + + """ + return self.namespace_manager.bind( + prefix, namespace, override=override) + + def namespaces(self): + """Generator over all the prefix, namespace tuples""" + for prefix, namespace in self.namespace_manager.namespaces(): + yield prefix, namespace + + def absolutize(self, uri, defrag=1): + """Turn uri into an absolute URI if it's not one already""" + return self.namespace_manager.absolutize(uri, defrag) + + def serialize(self, destination=None, format="xml", + base=None, encoding=None, **args): + """Serialize the Graph to destination + + If destination is None serialize method returns the serialization as a + string. Format defaults to xml (AKA rdf/xml). + + Format support can be extended with plugins, + but 'xml', 'n3', 'turtle', 'nt', 'pretty-xml', 'trix', 'trig' and 'nquads' are built in. + """ + serializer = plugin.get(format, Serializer)(self) + if destination is None: + stream = BytesIO() + serializer.serialize(stream, base=base, encoding=encoding, **args) + return stream.getvalue() + if hasattr(destination, "write"): + stream = destination + serializer.serialize(stream, base=base, encoding=encoding, **args) + else: + location = destination + scheme, netloc, path, params, _query, fragment = urlparse(location) + if netloc != "": + print(("WARNING: not saving as location" + + "is not a local file reference")) + return + fd, name = tempfile.mkstemp() + stream = os.fdopen(fd, "wb") + serializer.serialize(stream, base=base, encoding=encoding, **args) + stream.close() + if hasattr(shutil, "move"): + shutil.move(name, path) + else: + shutil.copy(name, path) + os.remove(name) + + def parse(self, source=None, publicID=None, format=None, + location=None, file=None, data=None, **args): + """ + Parse source adding the resulting triples to the Graph. + + The source is specified using one of source, location, file or + data. + + :Parameters: + + - `source`: An InputSource, file-like object, or string. In the case + of a string the string is the location of the source. + - `location`: A string indicating the relative or absolute URL of the + source. Graph's absolutize method is used if a relative location + is specified. + - `file`: A file-like object. + - `data`: A string containing the data to be parsed. + - `format`: Used if format can not be determined from source. + Defaults to rdf/xml. Format support can be extended with plugins, + but 'xml', 'n3', 'nt', 'trix', 'rdfa' are built in. + - `publicID`: the logical URI to use as the document base. If None + specified the document location is used (at least in the case where + there is a document location). + + :Returns: + + - self, the graph instance. + + Examples: + + >>> my_data = ''' + ... <rdf:RDF + ... xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' + ... xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#' + ... > + ... <rdf:Description> + ... <rdfs:label>Example</rdfs:label> + ... <rdfs:comment>This is really just an example.</rdfs:comment> + ... </rdf:Description> + ... </rdf:RDF> + ... ''' + >>> import tempfile + >>> fd, file_name = tempfile.mkstemp() + >>> f = os.fdopen(fd, 'w') + >>> dummy = f.write(my_data) # Returns num bytes written on py3 + >>> f.close() + + >>> g = Graph() + >>> result = g.parse(data=my_data, format="application/rdf+xml") + >>> len(g) + 2 + + >>> g = Graph() + >>> result = g.parse(location=file_name, format="application/rdf+xml") + >>> len(g) + 2 + + >>> g = Graph() + >>> with open(file_name, "r") as f: + ... result = g.parse(f, format="application/rdf+xml") + >>> len(g) + 2 + + >>> os.remove(file_name) + + """ + + source = create_input_source(source=source, publicID=publicID, + location=location, file=file, + data=data, format=format) + if format is None: + format = source.content_type + if format is None: + # raise Exception("Could not determine format for %r. You can" + \ + # "expicitly specify one with the format argument." % source) + format = "application/rdf+xml" + parser = plugin.get(format, Parser)() + try: + parser.parse(source, self, **args) + finally: + if source.auto_close: + source.close() + return self + + def load(self, source, publicID=None, format="xml"): + self.parse(source, publicID, format) + + def query(self, query_object, processor='sparql', + result='sparql', initNs=None, initBindings=None, + use_store_provided=True, **kwargs): + """ + Query this graph. + + A type of 'prepared queries' can be realised by providing + initial variable bindings with initBindings + + Initial namespaces are used to resolve prefixes used in the query, + if none are given, the namespaces from the graph's namespace manager + are used. + + :returntype: rdflib.query.QueryResult + + """ + + initBindings = initBindings or {} + initNs = initNs or dict(self.namespaces()) + + if hasattr(self.store, "query") and use_store_provided: + try: + return self.store.query( + query_object, initNs, initBindings, + self.default_union + and '__UNION__' + or self.identifier, + **kwargs) + except NotImplementedError: + pass # store has no own implementation + + if not isinstance(result, query.Result): + result = plugin.get(result, query.Result) + if not isinstance(processor, query.Processor): + processor = plugin.get(processor, query.Processor)(self) + + return result(processor.query( + query_object, initBindings, initNs, **kwargs)) + + def update(self, update_object, processor='sparql', + initNs=None, initBindings=None, + use_store_provided=True, **kwargs): + """Update this graph with the given update query.""" + initBindings = initBindings or {} + initNs = initNs or dict(self.namespaces()) + + if hasattr(self.store, "update") and use_store_provided: + try: + return self.store.update( + update_object, initNs, initBindings, + self.default_union + and '__UNION__' + or self.identifier, + **kwargs) + except NotImplementedError: + pass # store has no own implementation + + if not isinstance(processor, query.UpdateProcessor): + processor = plugin.get(processor, query.UpdateProcessor)(self) + + return processor.update(update_object, initBindings, initNs, **kwargs) + + + def n3(self): + """return an n3 identifier for the Graph""" + return "[%s]" % self.identifier.n3() + + def __reduce__(self): + return (Graph, (self.store, self.identifier,)) + + def isomorphic(self, other): + """ + does a very basic check if these graphs are the same + If no BNodes are involved, this is accurate. + + See rdflib.compare for a correct implementation of isomorphism checks + """ + # TODO: this is only an approximation. + if len(self) != len(other): + return False + for s, p, o in self: + if not isinstance(s, BNode) and not isinstance(o, BNode): + if not (s, p, o) in other: + return False + for s, p, o in other: + if not isinstance(s, BNode) and not isinstance(o, BNode): + if not (s, p, o) in self: + return False + # TODO: very well could be a false positive at this point yet. + return True + + def connected(self): + """Check if the Graph is connected + + The Graph is considered undirectional. + + Performs a search on the Graph, starting from a random node. Then + iteratively goes depth-first through the triplets where the node is + subject and object. Return True if all nodes have been visited and + False if it cannot continue and there are still unvisited nodes left. + """ + all_nodes = list(self.all_nodes()) + discovered = [] + + # take a random one, could also always take the first one, doesn't + # really matter. + if not all_nodes: + return False + + visiting = [all_nodes[random.randrange(len(all_nodes))]] + while visiting: + x = visiting.pop() + if x not in discovered: + discovered.append(x) + for new_x in self.objects(subject=x): + if new_x not in discovered and new_x not in visiting: + visiting.append(new_x) + for new_x in self.subjects(object=x): + if new_x not in discovered and new_x not in visiting: + visiting.append(new_x) + + # optimisation by only considering length, since no new objects can + # be introduced anywhere. + if len(all_nodes) == len(discovered): + return True + else: + return False + + def all_nodes(self): + res = set(self.objects()) + res.update(self.subjects()) + return res + + def collection(self, identifier): + """Create a new ``Collection`` instance. + + Parameters: + + - ``identifier``: a URIRef or BNode instance. + + Example:: + + >>> graph = Graph() + >>> uri = URIRef("http://example.org/resource") + >>> collection = graph.collection(uri) + >>> assert isinstance(collection, Collection) + >>> assert collection.uri is uri + >>> assert collection.graph is graph + >>> collection += [ Literal(1), Literal(2) ] + """ + + return Collection(self, identifier) + + + + def resource(self, identifier): + """Create a new ``Resource`` instance. + + Parameters: + + - ``identifier``: a URIRef or BNode instance. + + Example:: + + >>> graph = Graph() + >>> uri = URIRef("http://example.org/resource") + >>> resource = graph.resource(uri) + >>> assert isinstance(resource, Resource) + >>> assert resource.identifier is uri + >>> assert resource.graph is graph + + """ + if not isinstance(identifier, Node): + identifier = URIRef(identifier) + return Resource(self, identifier) + + def _process_skolem_tuples(self, target, func): + for t in self.triples((None, None, None)): + target.add(func(t)) + + def skolemize(self, new_graph=None, bnode=None): + def do_skolemize(bnode, t): + (s, p, o) = t + if s == bnode: + s = s.skolemize() + if o == bnode: + o = o.skolemize() + return (s, p, o) + + def do_skolemize2(t): + (s, p, o) = t + if isinstance(s, BNode): + s = s.skolemize() + if isinstance(o, BNode): + o = o.skolemize() + return (s, p, o) + + retval = Graph() if new_graph is None else new_graph + + if bnode is None: + self._process_skolem_tuples(retval, do_skolemize2) + elif isinstance(bnode, BNode): + self._process_skolem_tuples( + retval, lambda t: do_skolemize(bnode, t)) + + return retval + + def de_skolemize(self, new_graph=None, uriref=None): + def do_de_skolemize(uriref, t): + (s, p, o) = t + if s == uriref: + s = s.de_skolemize() + if o == uriref: + o = o.de_skolemize() + return (s, p, o) + + def do_de_skolemize2(t): + (s, p, o) = t + if isinstance(s, Genid): + s = s.de_skolemize() + if isinstance(o, Genid): + o = o.de_skolemize() + return (s, p, o) + + retval = Graph() if new_graph is None else new_graph + + if uriref is None: + self._process_skolem_tuples(retval, do_de_skolemize2) + elif isinstance(uriref, Genid): + self._process_skolem_tuples( + retval, lambda t: do_de_skolemize(uriref, t)) + + return retval + +class ConjunctiveGraph(Graph): + + """ + A ConjunctiveGraph is an (unnamed) aggregation of all the named + graphs in a store. + + It has a ``default`` graph, whose name is associated with the + graph throughout its life. :meth:`__init__` can take an identifier + to use as the name of this default graph or it will assign a + BNode. + + All methods that add triples work against this default graph. + + All queries are carried out against the union of all graphs. + + """ + + def __init__(self, store='default', identifier=None): + super(ConjunctiveGraph, self).__init__(store, identifier=identifier) + assert self.store.context_aware, ("ConjunctiveGraph must be backed by" + " a context aware store.") + self.context_aware = True + self.default_union = True # Conjunctive! + self.default_context = Graph(store=self.store, + identifier=identifier or BNode()) + + def __str__(self): + pattern = ("[a rdflib:ConjunctiveGraph;rdflib:storage " + "[a rdflib:Store;rdfs:label '%s']]") + return pattern % self.store.__class__.__name__ + + def _spoc(self, triple_or_quad, default=False): + """ + helper method for having methods that support + either triples or quads + """ + if triple_or_quad is None: + return (None, None, None, self.default_context if default else None) + if len(triple_or_quad) == 3: + c = self.default_context if default else None + (s, p, o) = triple_or_quad + elif len(triple_or_quad) == 4: + (s, p, o, c) = triple_or_quad + c = self._graph(c) + return s,p,o,c + + + def __contains__(self, triple_or_quad): + """Support for 'triple/quad in graph' syntax""" + s,p,o,c = self._spoc(triple_or_quad) + for t in self.triples((s,p,o), context=c): + return True + return False + + + def add(self, triple_or_quad): + + """ + Add a triple or quad to the store. + + if a triple is given it is added to the default context + """ + + s,p,o,c = self._spoc(triple_or_quad, default=True) + + _assertnode(s,p,o) + + self.store.add((s, p, o), context=c, quoted=False) + + def _graph(self, c): + if c is None: return None + if not isinstance(c, Graph): + return self.get_context(c) + else: + return c + + + def addN(self, quads): + """Add a sequence of triples with context""" + + self.store.addN( + (s, p, o, self._graph(c)) for s, p, o, c in quads if + _assertnode(s, p, o) + ) + + def remove(self, triple_or_quad): + """ + Removes a triple or quads + + if a triple is given it is removed from all contexts + + a quad is removed from the given context only + + """ + s,p,o,c = self._spoc(triple_or_quad) + + self.store.remove((s, p, o), context=c) + + def triples(self, triple_or_quad, context=None): + """ + Iterate over all the triples in the entire conjunctive graph + + For legacy reasons, this can take the context to query either + as a fourth element of the quad, or as the explicit context + keyword parameter. The kw param takes precedence. + """ + + s,p,o,c = self._spoc(triple_or_quad) + context = self._graph(context or c) + + if self.default_union: + if context==self.default_context: + context = None + else: + if context is None: + context = self.default_context + + if isinstance(p, Path): + if context is None: + context = self + + for s, o in p.eval(context, s, o): + yield (s, p, o) + else: + for (s, p, o), cg in self.store.triples((s, p, o), context=context): + yield s, p, o + + def quads(self, triple_or_quad=None): + """Iterate over all the quads in the entire conjunctive graph""" + + s,p,o,c = self._spoc(triple_or_quad) + + for (s, p, o), cg in self.store.triples((s, p, o), context=c): + for ctx in cg: + yield s, p, o, ctx + + def triples_choices(self, xxx_todo_changeme4, context=None): + """Iterate over all the triples in the entire conjunctive graph""" + (s, p, o) = xxx_todo_changeme4 + if context is None: + if not self.default_union: + context=self.default_context + else: + context = self._graph(context) + + for (s1, p1, o1), cg in self.store.triples_choices((s, p, o), + context=context): + yield (s1, p1, o1) + + def __len__(self): + """Number of triples in the entire conjunctive graph""" + return self.store.__len__() + + def contexts(self, triple=None): + """Iterate over all contexts in the graph + + If triple is specified, iterate over all contexts the triple is in. + """ + for context in self.store.contexts(triple): + if isinstance(context, Graph): + # TODO: One of these should never happen and probably + # should raise an exception rather than smoothing over + # the weirdness - see #225 + yield context + else: + yield self.get_context(context) + + def get_context(self, identifier, quoted=False): + """Return a context graph for the given identifier + + identifier must be a URIRef or BNode. + """ + return Graph(store=self.store, identifier=identifier, + namespace_manager=self) + + def remove_context(self, context): + """Removes the given context from the graph""" + self.store.remove((None, None, None), context) + + def context_id(self, uri, context_id=None): + """URI#context""" + uri = uri.split("#", 1)[0] + if context_id is None: + context_id = "#context" + return URIRef(context_id, base=uri) + + def parse(self, source=None, publicID=None, format="xml", + location=None, file=None, data=None, **args): + """ + Parse source adding the resulting triples to its own context + (sub graph of this graph). + + See :meth:`rdflib.graph.Graph.parse` for documentation on arguments. + + :Returns: + + The graph into which the source was parsed. In the case of n3 + it returns the root context. + """ + + source = create_input_source( + source=source, publicID=publicID, location=location, + file=file, data=data, format=format) + + g_id = publicID and publicID or source.getPublicId() + if not isinstance(g_id, Node): + g_id = URIRef(g_id) + + context = Graph(store=self.store, identifier=g_id) + context.remove((None, None, None)) # hmm ? + context.parse(source, publicID=publicID, format=format, **args) + return context + + def __reduce__(self): + return (ConjunctiveGraph, (self.store, self.identifier)) + + + +DATASET_DEFAULT_GRAPH_ID = URIRef('urn:x-rdflib:default') + +class Dataset(ConjunctiveGraph): + __doc__ = format_doctest_out(""" + RDF 1.1 Dataset. Small extension to the Conjunctive Graph: + - the primary term is graphs in the datasets and not contexts with quads, + so there is a separate method to set/retrieve a graph in a dataset and + operate with graphs + - graphs cannot be identified with blank nodes + - added a method to directly add a single quad + + Examples of usage: + + >>> # Create a new Dataset + >>> ds = Dataset() + >>> # simple triples goes to default graph + >>> ds.add((URIRef('http://example.org/a'), + ... URIRef('http://www.example.org/b'), + ... Literal('foo'))) + >>> + >>> # Create a graph in the dataset, if the graph name has already been + >>> # used, the corresponding graph will be returned + >>> # (ie, the Dataset keeps track of the constituent graphs) + >>> g = ds.graph(URIRef('http://www.example.com/gr')) + >>> + >>> # add triples to the new graph as usual + >>> g.add( + ... (URIRef('http://example.org/x'), + ... URIRef('http://example.org/y'), + ... Literal('bar')) ) + >>> # alternatively: add a quad to the dataset -> goes to the graph + >>> ds.add( + ... (URIRef('http://example.org/x'), + ... URIRef('http://example.org/z'), + ... Literal('foo-bar'),g) ) + >>> + >>> # querying triples return them all regardless of the graph + >>> for t in ds.triples((None,None,None)): # doctest: +SKIP + ... print(t) # doctest: +NORMALIZE_WHITESPACE + (rdflib.term.URIRef(%(u)s'http://example.org/a'), + rdflib.term.URIRef(%(u)s'http://www.example.org/b'), + rdflib.term.Literal(%(u)s'foo')) + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/z'), + rdflib.term.Literal(%(u)s'foo-bar')) + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/y'), + rdflib.term.Literal(%(u)s'bar')) + >>> + >>> # querying quads return quads; the fourth argument can be unrestricted + >>> # or restricted to a graph + >>> for q in ds.quads((None, None, None, None)): # doctest: +SKIP + ... print(q) # doctest: +NORMALIZE_WHITESPACE + (rdflib.term.URIRef(%(u)s'http://example.org/a'), + rdflib.term.URIRef(%(u)s'http://www.example.org/b'), + rdflib.term.Literal(%(u)s'foo'), + None) + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/y'), + rdflib.term.Literal(%(u)s'bar'), + rdflib.term.URIRef(%(u)s'http://www.example.com/gr')) + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/z'), + rdflib.term.Literal(%(u)s'foo-bar'), + rdflib.term.URIRef(%(u)s'http://www.example.com/gr')) + >>> + >>> for q in ds.quads((None,None,None,g)): # doctest: +SKIP + ... print(q) # doctest: +NORMALIZE_WHITESPACE + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/y'), + rdflib.term.Literal(%(u)s'bar'), + rdflib.term.URIRef(%(u)s'http://www.example.com/gr')) + (rdflib.term.URIRef(%(u)s'http://example.org/x'), + rdflib.term.URIRef(%(u)s'http://example.org/z'), + rdflib.term.Literal(%(u)s'foo-bar'), + rdflib.term.URIRef(%(u)s'http://www.example.com/gr')) + >>> # Note that in the call above - + >>> # ds.quads((None,None,None,'http://www.example.com/gr')) + >>> # would have been accepted, too + >>> + >>> # graph names in the dataset can be queried: + >>> for c in ds.graphs(): # doctest: +SKIP + ... print(c) # doctest: + DEFAULT + http://www.example.com/gr + >>> # A graph can be created without specifying a name; a skolemized genid + >>> # is created on the fly + >>> h = ds.graph() + >>> for c in ds.graphs(): # doctest: +SKIP + ... print(c) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + DEFAULT + http://rdlib.net/.well-known/genid/rdflib/N... + http://www.example.com/gr + >>> # Note that the Dataset.graphs() call returns names of empty graphs, + >>> # too. This can be restricted: + >>> for c in ds.graphs(empty=False): # doctest: +SKIP + ... print(c) # doctest: +NORMALIZE_WHITESPACE + DEFAULT + http://www.example.com/gr + >>> + >>> # a graph can also be removed from a dataset via ds.remove_graph(g) + + .. versionadded:: 4.0 + """) + + def __init__(self, store='default', default_union=False): + super(Dataset, self).__init__(store=store, identifier=None) + + if not self.store.graph_aware: + raise Exception("DataSet must be backed by a graph-aware store!") + self.default_context = Graph(store=self.store, identifier=DATASET_DEFAULT_GRAPH_ID) + + self.default_union = default_union + + + def __str__(self): + pattern = ("[a rdflib:Dataset;rdflib:storage " + "[a rdflib:Store;rdfs:label '%s']]") + return pattern % self.store.__class__.__name__ + + def graph(self, identifier=None): + if identifier is None: + from rdflib.term import rdflib_skolem_genid + self.bind( + "genid", "http://rdflib.net" + rdflib_skolem_genid, + override=False) + identifier = BNode().skolemize() + + g = self._graph(identifier) + + self.store.add_graph(g) + return g + + def parse(self, source=None, publicID=None, format="xml", + location=None, file=None, data=None, **args): + c = ConjunctiveGraph.parse(self, source, publicID, format, location, file, data, **args) + self.graph(c) + return c + + def add_graph(self, g): + """alias of graph for consistency""" + return self.graph(g) + + def remove_graph(self, g): + if not isinstance(g, Graph): + g = self.get_context(g) + + self.store.remove_graph(g) + if g is None or g == self.default_context: + # default graph cannot be removed + # only triples deleted, so add it back in + self.store.add_graph(self.default_context) + + def contexts(self, triple=None): + default = False + for c in super(Dataset, self).contexts(triple): + default |= c.identifier == DATASET_DEFAULT_GRAPH_ID + yield c + if not default: + yield self.graph(DATASET_DEFAULT_GRAPH_ID) + + graphs = contexts + + def quads(self, quad): + for s, p, o, c in super(Dataset, self).quads(quad): + if c.identifier==self.default_context: + yield (s, p, o, None) + else: + yield (s, p, o, c.identifier) + +class QuotedGraph(Graph): + """ + Quoted Graphs are intended to implement Notation 3 formulae. They are + associated with a required identifier that the N3 parser *must* provide + in order to maintain consistent formulae identification for scenarios + such as implication and other such processing. + """ + def __init__(self, store, identifier): + super(QuotedGraph, self).__init__(store, identifier) + + def add(self, xxx_todo_changeme5): + """Add a triple with self as context""" + (s, p, o) = xxx_todo_changeme5 + assert isinstance(s, Node), \ + "Subject %s must be an rdflib term" % (s,) + assert isinstance(p, Node), \ + "Predicate %s must be an rdflib term" % (p,) + assert isinstance(o, Node), \ + "Object %s must be an rdflib term" % (o,) + + self.store.add((s, p, o), self, quoted=True) + + def addN(self, quads): + """Add a sequence of triple with context""" + + self.store.addN( + (s, p, o, c) for s, p, o, c in quads + if isinstance(c, QuotedGraph) + and c.identifier is self.identifier + and _assertnode(s, p, o) + ) + + def n3(self): + """Return an n3 identifier for the Graph""" + return "{%s}" % self.identifier.n3() + + def __str__(self): + identifier = self.identifier.n3() + label = self.store.__class__.__name__ + pattern = ("{this rdflib.identifier %s;rdflib:storage " + "[a rdflib:Store;rdfs:label '%s']}") + return pattern % (identifier, label) + + def __reduce__(self): + return (QuotedGraph, (self.store, self.identifier)) + + +# Make sure QuotedGraph is ordered correctly +# wrt to other Terms. +# this must be done here, as the QuotedGraph cannot be +# circularily imported in term.py +rdflib.term._ORDERING[QuotedGraph]=11 + + +class Seq(object): + """Wrapper around an RDF Seq resource + + It implements a container type in Python with the order of the items + returned corresponding to the Seq content. It is based on the natural + ordering of the predicate names _1, _2, _3, etc, which is the + 'implementation' of a sequence in RDF terms. + """ + + def __init__(self, graph, subject): + """Parameters: + + - graph: + the graph containing the Seq + + - subject: + the subject of a Seq. Note that the init does not + check whether this is a Seq, this is done in whoever + creates this instance! + """ + + _list = self._list = list() + LI_INDEX = URIRef(str(RDF) + "_") + for (p, o) in graph.predicate_objects(subject): + if p.startswith(LI_INDEX): # != RDF.Seq: # + i = int(p.replace(LI_INDEX, '')) + _list.append((i, o)) + + # here is the trick: the predicates are _1, _2, _3, etc. Ie, + # by sorting the keys (by integer) we have what we want! + _list.sort() + + def toPython(self): + return self + + def __iter__(self): + """Generator over the items in the Seq""" + for _, item in self._list: + yield item + + def __len__(self): + """Length of the Seq""" + return len(self._list) + + def __getitem__(self, index): + """Item given by index from the Seq""" + index, item = self._list.__getitem__(index) + return item + + +class ModificationException(Exception): + + def __init__(self): + pass + + def __str__(self): + return ("Modifications and transactional operations not allowed on " + "ReadOnlyGraphAggregate instances") + + +class UnSupportedAggregateOperation(Exception): + + def __init__(self): + pass + + def __str__(self): + return ("This operation is not supported by ReadOnlyGraphAggregate " + "instances") + + +class ReadOnlyGraphAggregate(ConjunctiveGraph): + """Utility class for treating a set of graphs as a single graph + + Only read operations are supported (hence the name). Essentially a + ConjunctiveGraph over an explicit subset of the entire store. + """ + + def __init__(self, graphs, store='default'): + if store is not None: + super(ReadOnlyGraphAggregate, self).__init__(store) + Graph.__init__(self, store) + self.__namespace_manager = None + + assert isinstance(graphs, list) \ + and graphs \ + and [g for g in graphs if isinstance(g, Graph)], \ + "graphs argument must be a list of Graphs!!" + self.graphs = graphs + + def __repr__(self): + return "<ReadOnlyGraphAggregate: %s graphs>" % len(self.graphs) + + def destroy(self, configuration): + raise ModificationException() + + # Transactional interfaces (optional) + def commit(self): + raise ModificationException() + + def rollback(self): + raise ModificationException() + + def open(self, configuration, create=False): + # TODO: is there a use case for this method? + for graph in self.graphs: + graph.open(self, configuration, create) + + def close(self): + for graph in self.graphs: + graph.close() + + def add(self, xxx_todo_changeme6): + (s, p, o) = xxx_todo_changeme6 + raise ModificationException() + + def addN(self, quads): + raise ModificationException() + + def remove(self, xxx_todo_changeme7): + (s, p, o) = xxx_todo_changeme7 + raise ModificationException() + + def triples(self, xxx_todo_changeme8): + (s, p, o) = xxx_todo_changeme8 + for graph in self.graphs: + if isinstance(p, Path): + for s, o in p.eval(self, s, o): + yield s, p, o + else: + for s1, p1, o1 in graph.triples((s, p, o)): + yield (s1, p1, o1) + + def __contains__(self, triple_or_quad): + context = None + if len(triple_or_quad) == 4: + context = triple_or_quad[3] + for graph in self.graphs: + if context is None or graph.identifier == context.identifier: + if triple_or_quad[:3] in graph: + return True + return False + + def quads(self, xxx_todo_changeme9): + """Iterate over all the quads in the entire aggregate graph""" + (s, p, o) = xxx_todo_changeme9 + for graph in self.graphs: + for s1, p1, o1 in graph.triples((s, p, o)): + yield (s1, p1, o1, graph) + + def __len__(self): + return sum(len(g) for g in self.graphs) + + def __hash__(self): + raise UnSupportedAggregateOperation() + + def __cmp__(self, other): + if other is None: + return -1 + elif isinstance(other, Graph): + return -1 + elif isinstance(other, ReadOnlyGraphAggregate): + return cmp(self.graphs, other.graphs) + else: + return -1 + + def __iadd__(self, other): + raise ModificationException() + + def __isub__(self, other): + raise ModificationException() + + # Conv. methods + + def triples_choices(self, xxx_todo_changeme10, context=None): + (subject, predicate, object_) = xxx_todo_changeme10 + for graph in self.graphs: + choices = graph.triples_choices((subject, predicate, object_)) + for (s, p, o) in choices: + yield (s, p, o) + + def qname(self, uri): + if hasattr(self, 'namespace_manager') and self.namespace_manager: + return self.namespace_manager.qname(uri) + raise UnSupportedAggregateOperation() + + def compute_qname(self, uri, generate=True): + if hasattr(self, 'namespace_manager') and self.namespace_manager: + return self.namespace_manager.compute_qname(uri, generate) + raise UnSupportedAggregateOperation() + + def bind(self, prefix, namespace, override=True): + raise UnSupportedAggregateOperation() + + def namespaces(self): + if hasattr(self, 'namespace_manager'): + for prefix, namespace in self.namespace_manager.namespaces(): + yield prefix, namespace + else: + for graph in self.graphs: + for prefix, namespace in graph.namespaces(): + yield prefix, namespace + + def absolutize(self, uri, defrag=1): + raise UnSupportedAggregateOperation() + + def parse(self, source, publicID=None, format="xml", **args): + raise ModificationException() + + def n3(self): + raise UnSupportedAggregateOperation() + + def __reduce__(self): + raise UnSupportedAggregateOperation() + +def _assertnode(*terms): + for t in terms: + assert isinstance(t, Node), \ + 'Term %s must be an rdflib term' % (t,) + return True + + +def test(): + import doctest + doctest.testmod() + +if __name__ == '__main__': + test()