comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 from rdflib.term import Literal # required for doctests
2 assert Literal # avoid warning
3 from rdflib.namespace import Namespace # required for doctests
4 assert Namespace # avoid warning
5 from rdflib.py3compat import format_doctest_out
6
7 __doc__ = format_doctest_out("""\
8
9 RDFLib defines the following kinds of Graphs:
10
11 * :class:`~rdflib.graph.Graph`
12 * :class:`~rdflib.graph.QuotedGraph`
13 * :class:`~rdflib.graph.ConjunctiveGraph`
14 * :class:`~rdflib.graph.Dataset`
15
16 Graph
17 -----
18
19 An RDF graph is a set of RDF triples. Graphs support the python ``in``
20 operator, as well as iteration and some operations like union,
21 difference and intersection.
22
23 see :class:`~rdflib.graph.Graph`
24
25 Conjunctive Graph
26 -----------------
27
28 A Conjunctive Graph is the most relevant collection of graphs that are
29 considered to be the boundary for closed world assumptions. This
30 boundary is equivalent to that of the store instance (which is itself
31 uniquely identified and distinct from other instances of
32 :class:`Store` that signify other Conjunctive Graphs). It is
33 equivalent to all the named graphs within it and associated with a
34 ``_default_`` graph which is automatically assigned a :class:`BNode`
35 for an identifier - if one isn't given.
36
37 see :class:`~rdflib.graph.ConjunctiveGraph`
38
39 Quoted graph
40 ------------
41
42 The notion of an RDF graph [14] is extended to include the concept of
43 a formula node. A formula node may occur wherever any other kind of
44 node can appear. Associated with a formula node is an RDF graph that
45 is completely disjoint from all other graphs; i.e. has no nodes in
46 common with any other graph. (It may contain the same labels as other
47 RDF graphs; because this is, by definition, a separate graph,
48 considerations of tidiness do not apply between the graph at a formula
49 node and any other graph.)
50
51 This is intended to map the idea of "{ N3-expression }" that is used
52 by N3 into an RDF graph upon which RDF semantics is defined.
53
54 see :class:`~rdflib.graph.QuotedGraph`
55
56 Dataset
57 -------
58
59 The RDF 1.1 Dataset, a small extension to the Conjunctive Graph. The
60 primary term is "graphs in the datasets" and not "contexts with quads"
61 so there is a separate method to set/retrieve a graph in a dataset and
62 to operate with dataset graphs. As a consequence of this approach,
63 dataset graphs cannot be identified with blank nodes, a name is always
64 required (RDFLib will automatically add a name if one is not provided
65 at creation time). This implementation includes a convenience method
66 to directly add a single quad to a dataset graph.
67
68 see :class:`~rdflib.graph.Dataset`
69
70 Working with graphs
71 ===================
72
73 Instantiating Graphs with default store (IOMemory) and default identifier
74 (a BNode):
75
76 >>> g = Graph()
77 >>> g.store.__class__
78 <class 'rdflib.plugins.memory.IOMemory'>
79 >>> g.identifier.__class__
80 <class 'rdflib.term.BNode'>
81
82 Instantiating Graphs with a IOMemory store and an identifier -
83 <http://rdflib.net>:
84
85 >>> g = Graph('IOMemory', URIRef("http://rdflib.net"))
86 >>> g.identifier
87 rdflib.term.URIRef(%(u)s'http://rdflib.net')
88 >>> str(g) # doctest: +NORMALIZE_WHITESPACE
89 "<http://rdflib.net> a rdfg:Graph;rdflib:storage
90 [a rdflib:Store;rdfs:label 'IOMemory']."
91
92 Creating a ConjunctiveGraph - The top level container for all named Graphs
93 in a 'database':
94
95 >>> g = ConjunctiveGraph()
96 >>> str(g.default_context)
97 "[a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'IOMemory']]."
98
99 Adding / removing reified triples to Graph and iterating over it directly or
100 via triple pattern:
101
102 >>> g = Graph()
103 >>> statementId = BNode()
104 >>> print(len(g))
105 0
106 >>> g.add((statementId, RDF.type, RDF.Statement))
107 >>> g.add((statementId, RDF.subject,
108 ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph')))
109 >>> g.add((statementId, RDF.predicate, RDFS.label))
110 >>> g.add((statementId, RDF.object, Literal("Conjunctive Graph")))
111 >>> print(len(g))
112 4
113 >>> for s, p, o in g:
114 ... print(type(s))
115 ...
116 <class 'rdflib.term.BNode'>
117 <class 'rdflib.term.BNode'>
118 <class 'rdflib.term.BNode'>
119 <class 'rdflib.term.BNode'>
120
121 >>> for s, p, o in g.triples((None, RDF.object, None)):
122 ... print(o)
123 ...
124 Conjunctive Graph
125 >>> g.remove((statementId, RDF.type, RDF.Statement))
126 >>> print(len(g))
127 3
128
129 ``None`` terms in calls to :meth:`~rdflib.graph.Graph.triples` can be
130 thought of as "open variables".
131
132 Graph support set-theoretic operators, you can add/subtract graphs, as
133 well as intersection (with multiplication operator g1*g2) and xor (g1
134 ^ g2).
135
136 Note that BNode IDs are kept when doing set-theoretic operations, this
137 may or may not be what you want. Two named graphs within the same
138 application probably want share BNode IDs, two graphs with data from
139 different sources probably not. If your BNode IDs are all generated
140 by RDFLib they are UUIDs and unique.
141
142 >>> g1 = Graph()
143 >>> g2 = Graph()
144 >>> u = URIRef(%(u)s'http://example.com/foo')
145 >>> g1.add([u, RDFS.label, Literal('foo')])
146 >>> g1.add([u, RDFS.label, Literal('bar')])
147 >>> g2.add([u, RDFS.label, Literal('foo')])
148 >>> g2.add([u, RDFS.label, Literal('bing')])
149 >>> len(g1 + g2) # adds bing as label
150 3
151 >>> len(g1 - g2) # removes foo
152 1
153 >>> len(g1 * g2) # only foo
154 1
155 >>> g1 += g2 # now g1 contains everything
156
157
158 Graph Aggregation - ConjunctiveGraphs and ReadOnlyGraphAggregate within
159 the same store:
160
161 >>> store = plugin.get('IOMemory', Store)()
162 >>> g1 = Graph(store)
163 >>> g2 = Graph(store)
164 >>> g3 = Graph(store)
165 >>> stmt1 = BNode()
166 >>> stmt2 = BNode()
167 >>> stmt3 = BNode()
168 >>> g1.add((stmt1, RDF.type, RDF.Statement))
169 >>> g1.add((stmt1, RDF.subject,
170 ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph')))
171 >>> g1.add((stmt1, RDF.predicate, RDFS.label))
172 >>> g1.add((stmt1, RDF.object, Literal("Conjunctive Graph")))
173 >>> g2.add((stmt2, RDF.type, RDF.Statement))
174 >>> g2.add((stmt2, RDF.subject,
175 ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph')))
176 >>> g2.add((stmt2, RDF.predicate, RDF.type))
177 >>> g2.add((stmt2, RDF.object, RDFS.Class))
178 >>> g3.add((stmt3, RDF.type, RDF.Statement))
179 >>> g3.add((stmt3, RDF.subject,
180 ... URIRef(%(u)s'http://rdflib.net/store/ConjunctiveGraph')))
181 >>> g3.add((stmt3, RDF.predicate, RDFS.comment))
182 >>> g3.add((stmt3, RDF.object, Literal(
183 ... "The top-level aggregate graph - The sum " +
184 ... "of all named graphs within a Store")))
185 >>> len(list(ConjunctiveGraph(store).subjects(RDF.type, RDF.Statement)))
186 3
187 >>> len(list(ReadOnlyGraphAggregate([g1,g2]).subjects(
188 ... RDF.type, RDF.Statement)))
189 2
190
191 ConjunctiveGraphs have a :meth:`~rdflib.graph.ConjunctiveGraph.quads` method
192 which returns quads instead of triples, where the fourth item is the Graph
193 (or subclass thereof) instance in which the triple was asserted:
194
195 >>> uniqueGraphNames = set(
196 ... [graph.identifier for s, p, o, graph in ConjunctiveGraph(store
197 ... ).quads((None, RDF.predicate, None))])
198 >>> len(uniqueGraphNames)
199 3
200 >>> unionGraph = ReadOnlyGraphAggregate([g1, g2])
201 >>> uniqueGraphNames = set(
202 ... [graph.identifier for s, p, o, graph in unionGraph.quads(
203 ... (None, RDF.predicate, None))])
204 >>> len(uniqueGraphNames)
205 2
206
207 Parsing N3 from a string
208
209 >>> g2 = Graph()
210 >>> src = '''
211 ... @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
212 ... @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
213 ... [ a rdf:Statement ;
214 ... rdf:subject <http://rdflib.net/store#ConjunctiveGraph>;
215 ... rdf:predicate rdfs:label;
216 ... rdf:object "Conjunctive Graph" ] .
217 ... '''
218 >>> g2 = g2.parse(data=src, format='n3')
219 >>> print(len(g2))
220 4
221
222 Using Namespace class:
223
224 >>> RDFLib = Namespace('http://rdflib.net/')
225 >>> RDFLib.ConjunctiveGraph
226 rdflib.term.URIRef(%(u)s'http://rdflib.net/ConjunctiveGraph')
227 >>> RDFLib['Graph']
228 rdflib.term.URIRef(%(u)s'http://rdflib.net/Graph')
229
230 """)
231
232 import logging
233 logger = logging.getLogger(__name__)
234
235 # import md5
236 import random
237 import warnings
238
239 from hashlib import md5
240
241 try:
242 from io import BytesIO
243 assert BytesIO
244 except ImportError:
245 try:
246 from io import StringIO as BytesIO
247 assert BytesIO
248 except ImportError:
249 from io import StringIO as BytesIO
250 assert BytesIO
251
252 from rdflib.namespace import RDF, RDFS, SKOS
253
254 from rdflib import plugin, exceptions, query
255
256 from rdflib.term import Node, URIRef, Genid
257 from rdflib.term import BNode
258
259 import rdflib.term
260
261 from rdflib.paths import Path
262
263 from rdflib.store import Store
264 from rdflib.serializer import Serializer
265 from rdflib.parser import Parser
266 from rdflib.parser import create_input_source
267 from rdflib.namespace import NamespaceManager
268 from rdflib.resource import Resource
269 from rdflib.collection import Collection
270 from rdflib import py3compat
271 b = py3compat.b
272
273 import os
274 import shutil
275 import tempfile
276 from urllib.parse import urlparse
277
278 __all__ = [
279 'Graph', 'ConjunctiveGraph', 'QuotedGraph', 'Seq',
280 'ModificationException', 'Dataset',
281 'UnSupportedAggregateOperation', 'ReadOnlyGraphAggregate']
282
283
284 class Graph(Node):
285 """An RDF Graph
286
287 The constructor accepts one argument, the 'store'
288 that will be used to store the graph data (see the 'store'
289 package for stores currently shipped with rdflib).
290
291 Stores can be context-aware or unaware. Unaware stores take up
292 (some) less space but cannot support features that require
293 context, such as true merging/demerging of sub-graphs and
294 provenance.
295
296 The Graph constructor can take an identifier which identifies the Graph
297 by name. If none is given, the graph is assigned a BNode for its
298 identifier.
299 For more on named graphs, see: http://www.w3.org/2004/03/trix/
300
301 """
302
303 def __init__(self, store='default', identifier=None,
304 namespace_manager=None):
305 super(Graph, self).__init__()
306 self.__identifier = identifier or BNode()
307
308 if not isinstance(self.__identifier, Node):
309 self.__identifier = URIRef(self.__identifier)
310
311 if not isinstance(store, Store):
312 # TODO: error handling
313 self.__store = store = plugin.get(store, Store)()
314 else:
315 self.__store = store
316 self.__namespace_manager = namespace_manager
317 self.context_aware = False
318 self.formula_aware = False
319 self.default_union = False
320
321 def __get_store(self):
322 return self.__store
323 store = property(__get_store) # read-only attr
324
325 def __get_identifier(self):
326 return self.__identifier
327 identifier = property(__get_identifier) # read-only attr
328
329 def _get_namespace_manager(self):
330 if self.__namespace_manager is None:
331 self.__namespace_manager = NamespaceManager(self)
332 return self.__namespace_manager
333
334 def _set_namespace_manager(self, nm):
335 self.__namespace_manager = nm
336
337 namespace_manager = property(_get_namespace_manager,
338 _set_namespace_manager,
339 doc="this graph's namespace-manager")
340
341 def __repr__(self):
342 return "<Graph identifier=%s (%s)>" % (self.identifier, type(self))
343
344 def __str__(self):
345 if isinstance(self.identifier, URIRef):
346 return ("%s a rdfg:Graph;rdflib:storage " +
347 "[a rdflib:Store;rdfs:label '%s'].") % (
348 self.identifier.n3(),
349 self.store.__class__.__name__)
350 else:
351 return ("[a rdfg:Graph;rdflib:storage " +
352 "[a rdflib:Store;rdfs:label '%s']].") % (
353 self.store.__class__.__name__)
354
355 def toPython(self):
356 return self
357
358 def destroy(self, configuration):
359 """Destroy the store identified by `configuration` if supported"""
360 self.__store.destroy(configuration)
361
362 # Transactional interfaces (optional)
363 def commit(self):
364 """Commits active transactions"""
365 self.__store.commit()
366
367 def rollback(self):
368 """Rollback active transactions"""
369 self.__store.rollback()
370
371 def open(self, configuration, create=False):
372 """Open the graph store
373
374 Might be necessary for stores that require opening a connection to a
375 database or acquiring some resource.
376 """
377 return self.__store.open(configuration, create)
378
379 def close(self, commit_pending_transaction=False):
380 """Close the graph store
381
382 Might be necessary for stores that require closing a connection to a
383 database or releasing some resource.
384 """
385 self.__store.close(
386 commit_pending_transaction=commit_pending_transaction)
387
388 def add(self, xxx_todo_changeme):
389 """Add a triple with self as context"""
390 (s, p, o) = xxx_todo_changeme
391 assert isinstance(s, Node), \
392 "Subject %s must be an rdflib term" % (s,)
393 assert isinstance(p, Node), \
394 "Predicate %s must be an rdflib term" % (p,)
395 assert isinstance(o, Node), \
396 "Object %s must be an rdflib term" % (o,)
397 self.__store.add((s, p, o), self, quoted=False)
398
399 def addN(self, quads):
400 """Add a sequence of triple with context"""
401
402 self.__store.addN((s, p, o, c) for s, p, o, c in quads
403 if isinstance(c, Graph)
404 and c.identifier is self.identifier
405 and _assertnode(s,p,o)
406 )
407
408 def remove(self, xxx_todo_changeme1):
409 """Remove a triple from the graph
410
411 If the triple does not provide a context attribute, removes the triple
412 from all contexts.
413 """
414 (s, p, o) = xxx_todo_changeme1
415 self.__store.remove((s, p, o), context=self)
416
417 def triples(self, xxx_todo_changeme2):
418 """Generator over the triple store
419
420 Returns triples that match the given triple pattern. If triple pattern
421 does not provide a context, all contexts will be searched.
422 """
423 (s, p, o) = xxx_todo_changeme2
424 if isinstance(p, Path):
425 for _s, _o in p.eval(self, s, o):
426 yield (_s, p, _o)
427 else:
428 for (s, p, o), cg in self.__store.triples((s, p, o), context=self):
429 yield (s, p, o)
430
431 @py3compat.format_doctest_out
432 def __getitem__(self, item):
433 """
434 A graph can be "sliced" as a shortcut for the triples method
435 The python slice syntax is (ab)used for specifying triples.
436 A generator over matches is returned,
437 the returned tuples include only the parts not given
438
439 >>> import rdflib
440 >>> g = rdflib.Graph()
441 >>> g.add((rdflib.URIRef('urn:bob'), rdflib.RDFS.label, rdflib.Literal('Bob')))
442
443 >>> list(g[rdflib.URIRef('urn:bob')]) # all triples about bob
444 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'), rdflib.term.Literal(%(u)s'Bob'))]
445
446 >>> list(g[:rdflib.RDFS.label]) # all label triples
447 [(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.Literal(%(u)s'Bob'))]
448
449 >>> list(g[::rdflib.Literal('Bob')]) # all triples with bob as object
450 [(rdflib.term.URIRef(%(u)s'urn:bob'), rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'))]
451
452 Combined with SPARQL paths, more complex queries can be
453 written concisely:
454
455 Name of all Bobs friends:
456
457 g[bob : FOAF.knows/FOAF.name ]
458
459 Some label for Bob:
460
461 g[bob : DC.title|FOAF.name|RDFS.label]
462
463 All friends and friends of friends of Bob
464
465 g[bob : FOAF.knows * '+']
466
467 etc.
468
469 .. versionadded:: 4.0
470
471 """
472
473 if isinstance(item, slice):
474
475 s,p,o=item.start,item.stop,item.step
476 if s is None and p is None and o is None:
477 return self.triples((s,p,o))
478 elif s is None and p is None:
479 return self.subject_predicates(o)
480 elif s is None and o is None:
481 return self.subject_objects(p)
482 elif p is None and o is None:
483 return self.predicate_objects(s)
484 elif s is None:
485 return self.subjects(p,o)
486 elif p is None:
487 return self.predicates(s,o)
488 elif o is None:
489 return self.objects(s,p)
490 else:
491 # all given
492 return (s,p,o) in self
493
494 elif isinstance(item, (Path,Node)):
495
496 return self.predicate_objects(item)
497
498 else:
499 raise TypeError("You can only index a graph by a single rdflib term or path, or a slice of rdflib terms.")
500
501 def __len__(self):
502 """Returns the number of triples in the graph
503
504 If context is specified then the number of triples in the context is
505 returned instead.
506 """
507 return self.__store.__len__(context=self)
508
509 def __iter__(self):
510 """Iterates over all triples in the store"""
511 return self.triples((None, None, None))
512
513 def __contains__(self, triple):
514 """Support for 'triple in graph' syntax"""
515 for triple in self.triples(triple):
516 return True
517 return False
518
519 def __hash__(self):
520 return hash(self.identifier)
521
522 def md5_term_hash(self):
523 d = md5(str(self.identifier))
524 d.update("G")
525 return d.hexdigest()
526
527 def __cmp__(self, other):
528 if other is None:
529 return -1
530 elif isinstance(other, Graph):
531 return cmp(self.identifier, other.identifier)
532 else:
533 # Note if None is considered equivalent to owl:Nothing
534 # Then perhaps a graph with length 0 should be considered
535 # equivalent to None (if compared to it)?
536 return 1
537
538 def __eq__(self, other):
539 return isinstance(other, Graph) \
540 and self.identifier == other.identifier
541
542 def __lt__(self, other):
543 return (other is None) \
544 or (isinstance(other, Graph)
545 and self.identifier < other.identifier)
546
547 def __le__(self, other):
548 return self < other or self == other
549
550 def __gt__(self, other):
551 return (isinstance(other, Graph)
552 and self.identifier > other.identifier) \
553 or (other is not None)
554
555 def __ge__(self, other):
556 return self > other or self == other
557
558 def __iadd__(self, other):
559 """Add all triples in Graph other to Graph.
560 BNode IDs are not changed."""
561 self.addN((s, p, o, self) for s, p, o in other)
562 return self
563
564 def __isub__(self, other):
565 """Subtract all triples in Graph other from Graph.
566 BNode IDs are not changed."""
567 for triple in other:
568 self.remove(triple)
569 return self
570
571 def __add__(self, other):
572 """Set-theoretic union
573 BNode IDs are not changed."""
574 retval = Graph()
575 for (prefix, uri) in set(
576 list(self.namespaces()) + list(other.namespaces())):
577 retval.bind(prefix, uri)
578 for x in self:
579 retval.add(x)
580 for y in other:
581 retval.add(y)
582 return retval
583
584 def __mul__(self, other):
585 """Set-theoretic intersection.
586 BNode IDs are not changed."""
587 retval = Graph()
588 for x in other:
589 if x in self:
590 retval.add(x)
591 return retval
592
593 def __sub__(self, other):
594 """Set-theoretic difference.
595 BNode IDs are not changed."""
596 retval = Graph()
597 for x in self:
598 if not x in other:
599 retval.add(x)
600 return retval
601
602 def __xor__(self, other):
603 """Set-theoretic XOR.
604 BNode IDs are not changed."""
605 return (self - other) + (other - self)
606
607 __or__ = __add__
608 __and__ = __mul__
609
610 # Conv. methods
611
612 def set(self, triple):
613 """Convenience method to update the value of object
614
615 Remove any existing triples for subject and predicate before adding
616 (subject, predicate, object).
617 """
618 (subject, predicate, object_) = triple
619 assert subject is not None, \
620 "s can't be None in .set([s,p,o]), as it would remove (*, p, *)"
621 assert predicate is not None, \
622 "p can't be None in .set([s,p,o]), as it would remove (s, *, *)"
623 self.remove((subject, predicate, None))
624 self.add((subject, predicate, object_))
625
626 def subjects(self, predicate=None, object=None):
627 """A generator of subjects with the given predicate and object"""
628 for s, p, o in self.triples((None, predicate, object)):
629 yield s
630
631 def predicates(self, subject=None, object=None):
632 """A generator of predicates with the given subject and object"""
633 for s, p, o in self.triples((subject, None, object)):
634 yield p
635
636 def objects(self, subject=None, predicate=None):
637 """A generator of objects with the given subject and predicate"""
638 for s, p, o in self.triples((subject, predicate, None)):
639 yield o
640
641 def subject_predicates(self, object=None):
642 """A generator of (subject, predicate) tuples for the given object"""
643 for s, p, o in self.triples((None, None, object)):
644 yield s, p
645
646 def subject_objects(self, predicate=None):
647 """A generator of (subject, object) tuples for the given predicate"""
648 for s, p, o in self.triples((None, predicate, None)):
649 yield s, o
650
651 def predicate_objects(self, subject=None):
652 """A generator of (predicate, object) tuples for the given subject"""
653 for s, p, o in self.triples((subject, None, None)):
654 yield p, o
655
656 def triples_choices(self, xxx_todo_changeme3, context=None):
657 (subject, predicate, object_) = xxx_todo_changeme3
658 for (s, p, o), cg in self.store.triples_choices(
659 (subject, predicate, object_), context=self):
660 yield (s, p, o)
661
662 def value(self, subject=None, predicate=RDF.value, object=None,
663 default=None, any=True):
664 """Get a value for a pair of two criteria
665
666 Exactly one of subject, predicate, object must be None. Useful if one
667 knows that there may only be one value.
668
669 It is one of those situations that occur a lot, hence this
670 'macro' like utility
671
672 Parameters:
673 subject, predicate, object -- exactly one must be None
674 default -- value to be returned if no values found
675 any -- if True, return any value in the case there is more than one,
676 else, raise UniquenessError
677 """
678 retval = default
679
680 if (subject is None and predicate is None) or \
681 (subject is None and object is None) or \
682 (predicate is None and object is None):
683 return None
684
685 if object is None:
686 values = self.objects(subject, predicate)
687 if subject is None:
688 values = self.subjects(predicate, object)
689 if predicate is None:
690 values = self.predicates(subject, object)
691
692 try:
693 retval = next(values)
694 except StopIteration:
695 retval = default
696 else:
697 if any is False:
698 try:
699 next(values)
700 msg = ("While trying to find a value for (%s, %s, %s) the"
701 " following multiple values where found:\n" %
702 (subject, predicate, object))
703 triples = self.store.triples(
704 (subject, predicate, object), None)
705 for (s, p, o), contexts in triples:
706 msg += "(%s, %s, %s)\n (contexts: %s)\n" % (
707 s, p, o, list(contexts))
708 raise exceptions.UniquenessError(msg)
709 except StopIteration:
710 pass
711 return retval
712
713 def label(self, subject, default=''):
714 """Query for the RDFS.label of the subject
715
716 Return default if no label exists or any label if multiple exist.
717 """
718 if subject is None:
719 return default
720 return self.value(subject, RDFS.label, default=default, any=True)
721
722 @py3compat.format_doctest_out
723 def preferredLabel(self, subject, lang=None, default=None,
724 labelProperties=(SKOS.prefLabel, RDFS.label)):
725 """
726 Find the preferred label for subject.
727
728 By default prefers skos:prefLabels over rdfs:labels. In case at least
729 one prefLabel is found returns those, else returns labels. In case a
730 language string (e.g., 'en', 'de' or even '' for no lang-tagged
731 literals) is given, only such labels will be considered.
732
733 Return a list of (labelProp, label) pairs, where labelProp is either
734 skos:prefLabel or rdfs:label.
735
736 >>> from rdflib import ConjunctiveGraph, URIRef, RDFS, Literal
737 >>> from rdflib.namespace import SKOS
738 >>> from pprint import pprint
739 >>> g = ConjunctiveGraph()
740 >>> u = URIRef(%(u)s'http://example.com/foo')
741 >>> g.add([u, RDFS.label, Literal('foo')])
742 >>> g.add([u, RDFS.label, Literal('bar')])
743 >>> pprint(sorted(g.preferredLabel(u)))
744 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'),
745 rdflib.term.Literal(%(u)s'bar')),
746 (rdflib.term.URIRef(%(u)s'http://www.w3.org/2000/01/rdf-schema#label'),
747 rdflib.term.Literal(%(u)s'foo'))]
748 >>> g.add([u, SKOS.prefLabel, Literal('bla')])
749 >>> pprint(g.preferredLabel(u))
750 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'),
751 rdflib.term.Literal(%(u)s'bla'))]
752 >>> g.add([u, SKOS.prefLabel, Literal('blubb', lang='en')])
753 >>> sorted(g.preferredLabel(u)) #doctest: +NORMALIZE_WHITESPACE
754 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'),
755 rdflib.term.Literal(%(u)s'bla')),
756 (rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'),
757 rdflib.term.Literal(%(u)s'blubb', lang='en'))]
758 >>> g.preferredLabel(u, lang='') #doctest: +NORMALIZE_WHITESPACE
759 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'),
760 rdflib.term.Literal(%(u)s'bla'))]
761 >>> pprint(g.preferredLabel(u, lang='en'))
762 [(rdflib.term.URIRef(%(u)s'http://www.w3.org/2004/02/skos/core#prefLabel'),
763 rdflib.term.Literal(%(u)s'blubb', lang='en'))]
764 """
765
766 if default is None:
767 default = []
768
769 # setup the language filtering
770 if lang is not None:
771 if lang == '': # we only want not language-tagged literals
772 langfilter = lambda l: l.language is None
773 else:
774 langfilter = lambda l: l.language == lang
775 else: # we don't care about language tags
776 langfilter = lambda l: True
777
778 for labelProp in labelProperties:
779 labels = list(filter(langfilter, self.objects(subject, labelProp)))
780 if len(labels) == 0:
781 continue
782 else:
783 return [(labelProp, l) for l in labels]
784 return default
785
786 def comment(self, subject, default=''):
787 """Query for the RDFS.comment of the subject
788
789 Return default if no comment exists
790 """
791 if subject is None:
792 return default
793 return self.value(subject, RDFS.comment, default=default, any=True)
794
795 def items(self, list):
796 """Generator over all items in the resource specified by list
797
798 list is an RDF collection.
799 """
800 chain = set([list])
801 while list:
802 item = self.value(list, RDF.first)
803 if item is not None:
804 yield item
805 list = self.value(list, RDF.rest)
806 if list in chain:
807 raise ValueError("List contains a recursive rdf:rest reference")
808 chain.add(list)
809
810 def transitiveClosure(self, func, arg, seen=None):
811 """
812 Generates transitive closure of a user-defined
813 function against the graph
814
815 >>> from rdflib.collection import Collection
816 >>> g=Graph()
817 >>> a=BNode('foo')
818 >>> b=BNode('bar')
819 >>> c=BNode('baz')
820 >>> g.add((a,RDF.first,RDF.type))
821 >>> g.add((a,RDF.rest,b))
822 >>> g.add((b,RDF.first,RDFS.label))
823 >>> g.add((b,RDF.rest,c))
824 >>> g.add((c,RDF.first,RDFS.comment))
825 >>> g.add((c,RDF.rest,RDF.nil))
826 >>> def topList(node,g):
827 ... for s in g.subjects(RDF.rest,node):
828 ... yield s
829 >>> def reverseList(node,g):
830 ... for f in g.objects(node,RDF.first):
831 ... print(f)
832 ... for s in g.subjects(RDF.rest,node):
833 ... yield s
834
835 >>> [rt for rt in g.transitiveClosure(
836 ... topList,RDF.nil)] # doctest: +NORMALIZE_WHITESPACE
837 [rdflib.term.BNode('baz'),
838 rdflib.term.BNode('bar'),
839 rdflib.term.BNode('foo')]
840
841 >>> [rt for rt in g.transitiveClosure(
842 ... reverseList,RDF.nil)] # doctest: +NORMALIZE_WHITESPACE
843 http://www.w3.org/2000/01/rdf-schema#comment
844 http://www.w3.org/2000/01/rdf-schema#label
845 http://www.w3.org/1999/02/22-rdf-syntax-ns#type
846 [rdflib.term.BNode('baz'),
847 rdflib.term.BNode('bar'),
848 rdflib.term.BNode('foo')]
849
850 """
851 if seen is None:
852 seen = {}
853 elif arg in seen:
854 return
855 seen[arg] = 1
856 for rt in func(arg, self):
857 yield rt
858 for rt_2 in self.transitiveClosure(func, rt, seen):
859 yield rt_2
860
861 def transitive_objects(self, subject, property, remember=None):
862 """Transitively generate objects for the ``property`` relationship
863
864 Generated objects belong to the depth first transitive closure of the
865 ``property`` relationship starting at ``subject``.
866 """
867 if remember is None:
868 remember = {}
869 if subject in remember:
870 return
871 remember[subject] = 1
872 yield subject
873 for object in self.objects(subject, property):
874 for o in self.transitive_objects(object, property, remember):
875 yield o
876
877 def transitive_subjects(self, predicate, object, remember=None):
878 """Transitively generate objects for the ``property`` relationship
879
880 Generated objects belong to the depth first transitive closure of the
881 ``property`` relationship starting at ``subject``.
882 """
883 if remember is None:
884 remember = {}
885 if object in remember:
886 return
887 remember[object] = 1
888 yield object
889 for subject in self.subjects(predicate, object):
890 for s in self.transitive_subjects(predicate, subject, remember):
891 yield s
892
893 def seq(self, subject):
894 """Check if subject is an rdf:Seq
895
896 If yes, it returns a Seq class instance, None otherwise.
897 """
898 if (subject, RDF.type, RDF.Seq) in self:
899 return Seq(self, subject)
900 else:
901 return None
902
903 def qname(self, uri):
904 return self.namespace_manager.qname(uri)
905
906 def compute_qname(self, uri, generate=True):
907 return self.namespace_manager.compute_qname(uri, generate)
908
909 def bind(self, prefix, namespace, override=True):
910 """Bind prefix to namespace
911
912 If override is True will bind namespace to given prefix even
913 if namespace was already bound to a different prefix.
914
915 for example: graph.bind('foaf', 'http://xmlns.com/foaf/0.1/')
916
917 """
918 return self.namespace_manager.bind(
919 prefix, namespace, override=override)
920
921 def namespaces(self):
922 """Generator over all the prefix, namespace tuples"""
923 for prefix, namespace in self.namespace_manager.namespaces():
924 yield prefix, namespace
925
926 def absolutize(self, uri, defrag=1):
927 """Turn uri into an absolute URI if it's not one already"""
928 return self.namespace_manager.absolutize(uri, defrag)
929
930 def serialize(self, destination=None, format="xml",
931 base=None, encoding=None, **args):
932 """Serialize the Graph to destination
933
934 If destination is None serialize method returns the serialization as a
935 string. Format defaults to xml (AKA rdf/xml).
936
937 Format support can be extended with plugins,
938 but 'xml', 'n3', 'turtle', 'nt', 'pretty-xml', 'trix', 'trig' and 'nquads' are built in.
939 """
940 serializer = plugin.get(format, Serializer)(self)
941 if destination is None:
942 stream = BytesIO()
943 serializer.serialize(stream, base=base, encoding=encoding, **args)
944 return stream.getvalue()
945 if hasattr(destination, "write"):
946 stream = destination
947 serializer.serialize(stream, base=base, encoding=encoding, **args)
948 else:
949 location = destination
950 scheme, netloc, path, params, _query, fragment = urlparse(location)
951 if netloc != "":
952 print(("WARNING: not saving as location" +
953 "is not a local file reference"))
954 return
955 fd, name = tempfile.mkstemp()
956 stream = os.fdopen(fd, "wb")
957 serializer.serialize(stream, base=base, encoding=encoding, **args)
958 stream.close()
959 if hasattr(shutil, "move"):
960 shutil.move(name, path)
961 else:
962 shutil.copy(name, path)
963 os.remove(name)
964
965 def parse(self, source=None, publicID=None, format=None,
966 location=None, file=None, data=None, **args):
967 """
968 Parse source adding the resulting triples to the Graph.
969
970 The source is specified using one of source, location, file or
971 data.
972
973 :Parameters:
974
975 - `source`: An InputSource, file-like object, or string. In the case
976 of a string the string is the location of the source.
977 - `location`: A string indicating the relative or absolute URL of the
978 source. Graph's absolutize method is used if a relative location
979 is specified.
980 - `file`: A file-like object.
981 - `data`: A string containing the data to be parsed.
982 - `format`: Used if format can not be determined from source.
983 Defaults to rdf/xml. Format support can be extended with plugins,
984 but 'xml', 'n3', 'nt', 'trix', 'rdfa' are built in.
985 - `publicID`: the logical URI to use as the document base. If None
986 specified the document location is used (at least in the case where
987 there is a document location).
988
989 :Returns:
990
991 - self, the graph instance.
992
993 Examples:
994
995 >>> my_data = '''
996 ... <rdf:RDF
997 ... xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
998 ... xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'
999 ... >
1000 ... <rdf:Description>
1001 ... <rdfs:label>Example</rdfs:label>
1002 ... <rdfs:comment>This is really just an example.</rdfs:comment>
1003 ... </rdf:Description>
1004 ... </rdf:RDF>
1005 ... '''
1006 >>> import tempfile
1007 >>> fd, file_name = tempfile.mkstemp()
1008 >>> f = os.fdopen(fd, 'w')
1009 >>> dummy = f.write(my_data) # Returns num bytes written on py3
1010 >>> f.close()
1011
1012 >>> g = Graph()
1013 >>> result = g.parse(data=my_data, format="application/rdf+xml")
1014 >>> len(g)
1015 2
1016
1017 >>> g = Graph()
1018 >>> result = g.parse(location=file_name, format="application/rdf+xml")
1019 >>> len(g)
1020 2
1021
1022 >>> g = Graph()
1023 >>> with open(file_name, "r") as f:
1024 ... result = g.parse(f, format="application/rdf+xml")
1025 >>> len(g)
1026 2
1027
1028 >>> os.remove(file_name)
1029
1030 """
1031
1032 source = create_input_source(source=source, publicID=publicID,
1033 location=location, file=file,
1034 data=data, format=format)
1035 if format is None:
1036 format = source.content_type
1037 if format is None:
1038 # raise Exception("Could not determine format for %r. You can" + \
1039 # "expicitly specify one with the format argument." % source)
1040 format = "application/rdf+xml"
1041 parser = plugin.get(format, Parser)()
1042 try:
1043 parser.parse(source, self, **args)
1044 finally:
1045 if source.auto_close:
1046 source.close()
1047 return self
1048
1049 def load(self, source, publicID=None, format="xml"):
1050 self.parse(source, publicID, format)
1051
1052 def query(self, query_object, processor='sparql',
1053 result='sparql', initNs=None, initBindings=None,
1054 use_store_provided=True, **kwargs):
1055 """
1056 Query this graph.
1057
1058 A type of 'prepared queries' can be realised by providing
1059 initial variable bindings with initBindings
1060
1061 Initial namespaces are used to resolve prefixes used in the query,
1062 if none are given, the namespaces from the graph's namespace manager
1063 are used.
1064
1065 :returntype: rdflib.query.QueryResult
1066
1067 """
1068
1069 initBindings = initBindings or {}
1070 initNs = initNs or dict(self.namespaces())
1071
1072 if hasattr(self.store, "query") and use_store_provided:
1073 try:
1074 return self.store.query(
1075 query_object, initNs, initBindings,
1076 self.default_union
1077 and '__UNION__'
1078 or self.identifier,
1079 **kwargs)
1080 except NotImplementedError:
1081 pass # store has no own implementation
1082
1083 if not isinstance(result, query.Result):
1084 result = plugin.get(result, query.Result)
1085 if not isinstance(processor, query.Processor):
1086 processor = plugin.get(processor, query.Processor)(self)
1087
1088 return result(processor.query(
1089 query_object, initBindings, initNs, **kwargs))
1090
1091 def update(self, update_object, processor='sparql',
1092 initNs=None, initBindings=None,
1093 use_store_provided=True, **kwargs):
1094 """Update this graph with the given update query."""
1095 initBindings = initBindings or {}
1096 initNs = initNs or dict(self.namespaces())
1097
1098 if hasattr(self.store, "update") and use_store_provided:
1099 try:
1100 return self.store.update(
1101 update_object, initNs, initBindings,
1102 self.default_union
1103 and '__UNION__'
1104 or self.identifier,
1105 **kwargs)
1106 except NotImplementedError:
1107 pass # store has no own implementation
1108
1109 if not isinstance(processor, query.UpdateProcessor):
1110 processor = plugin.get(processor, query.UpdateProcessor)(self)
1111
1112 return processor.update(update_object, initBindings, initNs, **kwargs)
1113
1114
1115 def n3(self):
1116 """return an n3 identifier for the Graph"""
1117 return "[%s]" % self.identifier.n3()
1118
1119 def __reduce__(self):
1120 return (Graph, (self.store, self.identifier,))
1121
1122 def isomorphic(self, other):
1123 """
1124 does a very basic check if these graphs are the same
1125 If no BNodes are involved, this is accurate.
1126
1127 See rdflib.compare for a correct implementation of isomorphism checks
1128 """
1129 # TODO: this is only an approximation.
1130 if len(self) != len(other):
1131 return False
1132 for s, p, o in self:
1133 if not isinstance(s, BNode) and not isinstance(o, BNode):
1134 if not (s, p, o) in other:
1135 return False
1136 for s, p, o in other:
1137 if not isinstance(s, BNode) and not isinstance(o, BNode):
1138 if not (s, p, o) in self:
1139 return False
1140 # TODO: very well could be a false positive at this point yet.
1141 return True
1142
1143 def connected(self):
1144 """Check if the Graph is connected
1145
1146 The Graph is considered undirectional.
1147
1148 Performs a search on the Graph, starting from a random node. Then
1149 iteratively goes depth-first through the triplets where the node is
1150 subject and object. Return True if all nodes have been visited and
1151 False if it cannot continue and there are still unvisited nodes left.
1152 """
1153 all_nodes = list(self.all_nodes())
1154 discovered = []
1155
1156 # take a random one, could also always take the first one, doesn't
1157 # really matter.
1158 if not all_nodes:
1159 return False
1160
1161 visiting = [all_nodes[random.randrange(len(all_nodes))]]
1162 while visiting:
1163 x = visiting.pop()
1164 if x not in discovered:
1165 discovered.append(x)
1166 for new_x in self.objects(subject=x):
1167 if new_x not in discovered and new_x not in visiting:
1168 visiting.append(new_x)
1169 for new_x in self.subjects(object=x):
1170 if new_x not in discovered and new_x not in visiting:
1171 visiting.append(new_x)
1172
1173 # optimisation by only considering length, since no new objects can
1174 # be introduced anywhere.
1175 if len(all_nodes) == len(discovered):
1176 return True
1177 else:
1178 return False
1179
1180 def all_nodes(self):
1181 res = set(self.objects())
1182 res.update(self.subjects())
1183 return res
1184
1185 def collection(self, identifier):
1186 """Create a new ``Collection`` instance.
1187
1188 Parameters:
1189
1190 - ``identifier``: a URIRef or BNode instance.
1191
1192 Example::
1193
1194 >>> graph = Graph()
1195 >>> uri = URIRef("http://example.org/resource")
1196 >>> collection = graph.collection(uri)
1197 >>> assert isinstance(collection, Collection)
1198 >>> assert collection.uri is uri
1199 >>> assert collection.graph is graph
1200 >>> collection += [ Literal(1), Literal(2) ]
1201 """
1202
1203 return Collection(self, identifier)
1204
1205
1206
1207 def resource(self, identifier):
1208 """Create a new ``Resource`` instance.
1209
1210 Parameters:
1211
1212 - ``identifier``: a URIRef or BNode instance.
1213
1214 Example::
1215
1216 >>> graph = Graph()
1217 >>> uri = URIRef("http://example.org/resource")
1218 >>> resource = graph.resource(uri)
1219 >>> assert isinstance(resource, Resource)
1220 >>> assert resource.identifier is uri
1221 >>> assert resource.graph is graph
1222
1223 """
1224 if not isinstance(identifier, Node):
1225 identifier = URIRef(identifier)
1226 return Resource(self, identifier)
1227
1228 def _process_skolem_tuples(self, target, func):
1229 for t in self.triples((None, None, None)):
1230 target.add(func(t))
1231
1232 def skolemize(self, new_graph=None, bnode=None):
1233 def do_skolemize(bnode, t):
1234 (s, p, o) = t
1235 if s == bnode:
1236 s = s.skolemize()
1237 if o == bnode:
1238 o = o.skolemize()
1239 return (s, p, o)
1240
1241 def do_skolemize2(t):
1242 (s, p, o) = t
1243 if isinstance(s, BNode):
1244 s = s.skolemize()
1245 if isinstance(o, BNode):
1246 o = o.skolemize()
1247 return (s, p, o)
1248
1249 retval = Graph() if new_graph is None else new_graph
1250
1251 if bnode is None:
1252 self._process_skolem_tuples(retval, do_skolemize2)
1253 elif isinstance(bnode, BNode):
1254 self._process_skolem_tuples(
1255 retval, lambda t: do_skolemize(bnode, t))
1256
1257 return retval
1258
1259 def de_skolemize(self, new_graph=None, uriref=None):
1260 def do_de_skolemize(uriref, t):
1261 (s, p, o) = t
1262 if s == uriref:
1263 s = s.de_skolemize()
1264 if o == uriref:
1265 o = o.de_skolemize()
1266 return (s, p, o)
1267
1268 def do_de_skolemize2(t):
1269 (s, p, o) = t
1270 if isinstance(s, Genid):
1271 s = s.de_skolemize()
1272 if isinstance(o, Genid):
1273 o = o.de_skolemize()
1274 return (s, p, o)
1275
1276 retval = Graph() if new_graph is None else new_graph
1277
1278 if uriref is None:
1279 self._process_skolem_tuples(retval, do_de_skolemize2)
1280 elif isinstance(uriref, Genid):
1281 self._process_skolem_tuples(
1282 retval, lambda t: do_de_skolemize(uriref, t))
1283
1284 return retval
1285
1286 class ConjunctiveGraph(Graph):
1287
1288 """
1289 A ConjunctiveGraph is an (unnamed) aggregation of all the named
1290 graphs in a store.
1291
1292 It has a ``default`` graph, whose name is associated with the
1293 graph throughout its life. :meth:`__init__` can take an identifier
1294 to use as the name of this default graph or it will assign a
1295 BNode.
1296
1297 All methods that add triples work against this default graph.
1298
1299 All queries are carried out against the union of all graphs.
1300
1301 """
1302
1303 def __init__(self, store='default', identifier=None):
1304 super(ConjunctiveGraph, self).__init__(store, identifier=identifier)
1305 assert self.store.context_aware, ("ConjunctiveGraph must be backed by"
1306 " a context aware store.")
1307 self.context_aware = True
1308 self.default_union = True # Conjunctive!
1309 self.default_context = Graph(store=self.store,
1310 identifier=identifier or BNode())
1311
1312 def __str__(self):
1313 pattern = ("[a rdflib:ConjunctiveGraph;rdflib:storage "
1314 "[a rdflib:Store;rdfs:label '%s']]")
1315 return pattern % self.store.__class__.__name__
1316
1317 def _spoc(self, triple_or_quad, default=False):
1318 """
1319 helper method for having methods that support
1320 either triples or quads
1321 """
1322 if triple_or_quad is None:
1323 return (None, None, None, self.default_context if default else None)
1324 if len(triple_or_quad) == 3:
1325 c = self.default_context if default else None
1326 (s, p, o) = triple_or_quad
1327 elif len(triple_or_quad) == 4:
1328 (s, p, o, c) = triple_or_quad
1329 c = self._graph(c)
1330 return s,p,o,c
1331
1332
1333 def __contains__(self, triple_or_quad):
1334 """Support for 'triple/quad in graph' syntax"""
1335 s,p,o,c = self._spoc(triple_or_quad)
1336 for t in self.triples((s,p,o), context=c):
1337 return True
1338 return False
1339
1340
1341 def add(self, triple_or_quad):
1342
1343 """
1344 Add a triple or quad to the store.
1345
1346 if a triple is given it is added to the default context
1347 """
1348
1349 s,p,o,c = self._spoc(triple_or_quad, default=True)
1350
1351 _assertnode(s,p,o)
1352
1353 self.store.add((s, p, o), context=c, quoted=False)
1354
1355 def _graph(self, c):
1356 if c is None: return None
1357 if not isinstance(c, Graph):
1358 return self.get_context(c)
1359 else:
1360 return c
1361
1362
1363 def addN(self, quads):
1364 """Add a sequence of triples with context"""
1365
1366 self.store.addN(
1367 (s, p, o, self._graph(c)) for s, p, o, c in quads if
1368 _assertnode(s, p, o)
1369 )
1370
1371 def remove(self, triple_or_quad):
1372 """
1373 Removes a triple or quads
1374
1375 if a triple is given it is removed from all contexts
1376
1377 a quad is removed from the given context only
1378
1379 """
1380 s,p,o,c = self._spoc(triple_or_quad)
1381
1382 self.store.remove((s, p, o), context=c)
1383
1384 def triples(self, triple_or_quad, context=None):
1385 """
1386 Iterate over all the triples in the entire conjunctive graph
1387
1388 For legacy reasons, this can take the context to query either
1389 as a fourth element of the quad, or as the explicit context
1390 keyword parameter. The kw param takes precedence.
1391 """
1392
1393 s,p,o,c = self._spoc(triple_or_quad)
1394 context = self._graph(context or c)
1395
1396 if self.default_union:
1397 if context==self.default_context:
1398 context = None
1399 else:
1400 if context is None:
1401 context = self.default_context
1402
1403 if isinstance(p, Path):
1404 if context is None:
1405 context = self
1406
1407 for s, o in p.eval(context, s, o):
1408 yield (s, p, o)
1409 else:
1410 for (s, p, o), cg in self.store.triples((s, p, o), context=context):
1411 yield s, p, o
1412
1413 def quads(self, triple_or_quad=None):
1414 """Iterate over all the quads in the entire conjunctive graph"""
1415
1416 s,p,o,c = self._spoc(triple_or_quad)
1417
1418 for (s, p, o), cg in self.store.triples((s, p, o), context=c):
1419 for ctx in cg:
1420 yield s, p, o, ctx
1421
1422 def triples_choices(self, xxx_todo_changeme4, context=None):
1423 """Iterate over all the triples in the entire conjunctive graph"""
1424 (s, p, o) = xxx_todo_changeme4
1425 if context is None:
1426 if not self.default_union:
1427 context=self.default_context
1428 else:
1429 context = self._graph(context)
1430
1431 for (s1, p1, o1), cg in self.store.triples_choices((s, p, o),
1432 context=context):
1433 yield (s1, p1, o1)
1434
1435 def __len__(self):
1436 """Number of triples in the entire conjunctive graph"""
1437 return self.store.__len__()
1438
1439 def contexts(self, triple=None):
1440 """Iterate over all contexts in the graph
1441
1442 If triple is specified, iterate over all contexts the triple is in.
1443 """
1444 for context in self.store.contexts(triple):
1445 if isinstance(context, Graph):
1446 # TODO: One of these should never happen and probably
1447 # should raise an exception rather than smoothing over
1448 # the weirdness - see #225
1449 yield context
1450 else:
1451 yield self.get_context(context)
1452
1453 def get_context(self, identifier, quoted=False):
1454 """Return a context graph for the given identifier
1455
1456 identifier must be a URIRef or BNode.
1457 """
1458 return Graph(store=self.store, identifier=identifier,
1459 namespace_manager=self)
1460
1461 def remove_context(self, context):
1462 """Removes the given context from the graph"""
1463 self.store.remove((None, None, None), context)
1464
1465 def context_id(self, uri, context_id=None):
1466 """URI#context"""
1467 uri = uri.split("#", 1)[0]
1468 if context_id is None:
1469 context_id = "#context"
1470 return URIRef(context_id, base=uri)
1471
1472 def parse(self, source=None, publicID=None, format="xml",
1473 location=None, file=None, data=None, **args):
1474 """
1475 Parse source adding the resulting triples to its own context
1476 (sub graph of this graph).
1477
1478 See :meth:`rdflib.graph.Graph.parse` for documentation on arguments.
1479
1480 :Returns:
1481
1482 The graph into which the source was parsed. In the case of n3
1483 it returns the root context.
1484 """
1485
1486 source = create_input_source(
1487 source=source, publicID=publicID, location=location,
1488 file=file, data=data, format=format)
1489
1490 g_id = publicID and publicID or source.getPublicId()
1491 if not isinstance(g_id, Node):
1492 g_id = URIRef(g_id)
1493
1494 context = Graph(store=self.store, identifier=g_id)
1495 context.remove((None, None, None)) # hmm ?
1496 context.parse(source, publicID=publicID, format=format, **args)
1497 return context
1498
1499 def __reduce__(self):
1500 return (ConjunctiveGraph, (self.store, self.identifier))
1501
1502
1503
1504 DATASET_DEFAULT_GRAPH_ID = URIRef('urn:x-rdflib:default')
1505
1506 class Dataset(ConjunctiveGraph):
1507 __doc__ = format_doctest_out("""
1508 RDF 1.1 Dataset. Small extension to the Conjunctive Graph:
1509 - the primary term is graphs in the datasets and not contexts with quads,
1510 so there is a separate method to set/retrieve a graph in a dataset and
1511 operate with graphs
1512 - graphs cannot be identified with blank nodes
1513 - added a method to directly add a single quad
1514
1515 Examples of usage:
1516
1517 >>> # Create a new Dataset
1518 >>> ds = Dataset()
1519 >>> # simple triples goes to default graph
1520 >>> ds.add((URIRef('http://example.org/a'),
1521 ... URIRef('http://www.example.org/b'),
1522 ... Literal('foo')))
1523 >>>
1524 >>> # Create a graph in the dataset, if the graph name has already been
1525 >>> # used, the corresponding graph will be returned
1526 >>> # (ie, the Dataset keeps track of the constituent graphs)
1527 >>> g = ds.graph(URIRef('http://www.example.com/gr'))
1528 >>>
1529 >>> # add triples to the new graph as usual
1530 >>> g.add(
1531 ... (URIRef('http://example.org/x'),
1532 ... URIRef('http://example.org/y'),
1533 ... Literal('bar')) )
1534 >>> # alternatively: add a quad to the dataset -> goes to the graph
1535 >>> ds.add(
1536 ... (URIRef('http://example.org/x'),
1537 ... URIRef('http://example.org/z'),
1538 ... Literal('foo-bar'),g) )
1539 >>>
1540 >>> # querying triples return them all regardless of the graph
1541 >>> for t in ds.triples((None,None,None)): # doctest: +SKIP
1542 ... print(t) # doctest: +NORMALIZE_WHITESPACE
1543 (rdflib.term.URIRef(%(u)s'http://example.org/a'),
1544 rdflib.term.URIRef(%(u)s'http://www.example.org/b'),
1545 rdflib.term.Literal(%(u)s'foo'))
1546 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1547 rdflib.term.URIRef(%(u)s'http://example.org/z'),
1548 rdflib.term.Literal(%(u)s'foo-bar'))
1549 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1550 rdflib.term.URIRef(%(u)s'http://example.org/y'),
1551 rdflib.term.Literal(%(u)s'bar'))
1552 >>>
1553 >>> # querying quads return quads; the fourth argument can be unrestricted
1554 >>> # or restricted to a graph
1555 >>> for q in ds.quads((None, None, None, None)): # doctest: +SKIP
1556 ... print(q) # doctest: +NORMALIZE_WHITESPACE
1557 (rdflib.term.URIRef(%(u)s'http://example.org/a'),
1558 rdflib.term.URIRef(%(u)s'http://www.example.org/b'),
1559 rdflib.term.Literal(%(u)s'foo'),
1560 None)
1561 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1562 rdflib.term.URIRef(%(u)s'http://example.org/y'),
1563 rdflib.term.Literal(%(u)s'bar'),
1564 rdflib.term.URIRef(%(u)s'http://www.example.com/gr'))
1565 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1566 rdflib.term.URIRef(%(u)s'http://example.org/z'),
1567 rdflib.term.Literal(%(u)s'foo-bar'),
1568 rdflib.term.URIRef(%(u)s'http://www.example.com/gr'))
1569 >>>
1570 >>> for q in ds.quads((None,None,None,g)): # doctest: +SKIP
1571 ... print(q) # doctest: +NORMALIZE_WHITESPACE
1572 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1573 rdflib.term.URIRef(%(u)s'http://example.org/y'),
1574 rdflib.term.Literal(%(u)s'bar'),
1575 rdflib.term.URIRef(%(u)s'http://www.example.com/gr'))
1576 (rdflib.term.URIRef(%(u)s'http://example.org/x'),
1577 rdflib.term.URIRef(%(u)s'http://example.org/z'),
1578 rdflib.term.Literal(%(u)s'foo-bar'),
1579 rdflib.term.URIRef(%(u)s'http://www.example.com/gr'))
1580 >>> # Note that in the call above -
1581 >>> # ds.quads((None,None,None,'http://www.example.com/gr'))
1582 >>> # would have been accepted, too
1583 >>>
1584 >>> # graph names in the dataset can be queried:
1585 >>> for c in ds.graphs(): # doctest: +SKIP
1586 ... print(c) # doctest:
1587 DEFAULT
1588 http://www.example.com/gr
1589 >>> # A graph can be created without specifying a name; a skolemized genid
1590 >>> # is created on the fly
1591 >>> h = ds.graph()
1592 >>> for c in ds.graphs(): # doctest: +SKIP
1593 ... print(c) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1594 DEFAULT
1595 http://rdlib.net/.well-known/genid/rdflib/N...
1596 http://www.example.com/gr
1597 >>> # Note that the Dataset.graphs() call returns names of empty graphs,
1598 >>> # too. This can be restricted:
1599 >>> for c in ds.graphs(empty=False): # doctest: +SKIP
1600 ... print(c) # doctest: +NORMALIZE_WHITESPACE
1601 DEFAULT
1602 http://www.example.com/gr
1603 >>>
1604 >>> # a graph can also be removed from a dataset via ds.remove_graph(g)
1605
1606 .. versionadded:: 4.0
1607 """)
1608
1609 def __init__(self, store='default', default_union=False):
1610 super(Dataset, self).__init__(store=store, identifier=None)
1611
1612 if not self.store.graph_aware:
1613 raise Exception("DataSet must be backed by a graph-aware store!")
1614 self.default_context = Graph(store=self.store, identifier=DATASET_DEFAULT_GRAPH_ID)
1615
1616 self.default_union = default_union
1617
1618
1619 def __str__(self):
1620 pattern = ("[a rdflib:Dataset;rdflib:storage "
1621 "[a rdflib:Store;rdfs:label '%s']]")
1622 return pattern % self.store.__class__.__name__
1623
1624 def graph(self, identifier=None):
1625 if identifier is None:
1626 from rdflib.term import rdflib_skolem_genid
1627 self.bind(
1628 "genid", "http://rdflib.net" + rdflib_skolem_genid,
1629 override=False)
1630 identifier = BNode().skolemize()
1631
1632 g = self._graph(identifier)
1633
1634 self.store.add_graph(g)
1635 return g
1636
1637 def parse(self, source=None, publicID=None, format="xml",
1638 location=None, file=None, data=None, **args):
1639 c = ConjunctiveGraph.parse(self, source, publicID, format, location, file, data, **args)
1640 self.graph(c)
1641 return c
1642
1643 def add_graph(self, g):
1644 """alias of graph for consistency"""
1645 return self.graph(g)
1646
1647 def remove_graph(self, g):
1648 if not isinstance(g, Graph):
1649 g = self.get_context(g)
1650
1651 self.store.remove_graph(g)
1652 if g is None or g == self.default_context:
1653 # default graph cannot be removed
1654 # only triples deleted, so add it back in
1655 self.store.add_graph(self.default_context)
1656
1657 def contexts(self, triple=None):
1658 default = False
1659 for c in super(Dataset, self).contexts(triple):
1660 default |= c.identifier == DATASET_DEFAULT_GRAPH_ID
1661 yield c
1662 if not default:
1663 yield self.graph(DATASET_DEFAULT_GRAPH_ID)
1664
1665 graphs = contexts
1666
1667 def quads(self, quad):
1668 for s, p, o, c in super(Dataset, self).quads(quad):
1669 if c.identifier==self.default_context:
1670 yield (s, p, o, None)
1671 else:
1672 yield (s, p, o, c.identifier)
1673
1674 class QuotedGraph(Graph):
1675 """
1676 Quoted Graphs are intended to implement Notation 3 formulae. They are
1677 associated with a required identifier that the N3 parser *must* provide
1678 in order to maintain consistent formulae identification for scenarios
1679 such as implication and other such processing.
1680 """
1681 def __init__(self, store, identifier):
1682 super(QuotedGraph, self).__init__(store, identifier)
1683
1684 def add(self, xxx_todo_changeme5):
1685 """Add a triple with self as context"""
1686 (s, p, o) = xxx_todo_changeme5
1687 assert isinstance(s, Node), \
1688 "Subject %s must be an rdflib term" % (s,)
1689 assert isinstance(p, Node), \
1690 "Predicate %s must be an rdflib term" % (p,)
1691 assert isinstance(o, Node), \
1692 "Object %s must be an rdflib term" % (o,)
1693
1694 self.store.add((s, p, o), self, quoted=True)
1695
1696 def addN(self, quads):
1697 """Add a sequence of triple with context"""
1698
1699 self.store.addN(
1700 (s, p, o, c) for s, p, o, c in quads
1701 if isinstance(c, QuotedGraph)
1702 and c.identifier is self.identifier
1703 and _assertnode(s, p, o)
1704 )
1705
1706 def n3(self):
1707 """Return an n3 identifier for the Graph"""
1708 return "{%s}" % self.identifier.n3()
1709
1710 def __str__(self):
1711 identifier = self.identifier.n3()
1712 label = self.store.__class__.__name__
1713 pattern = ("{this rdflib.identifier %s;rdflib:storage "
1714 "[a rdflib:Store;rdfs:label '%s']}")
1715 return pattern % (identifier, label)
1716
1717 def __reduce__(self):
1718 return (QuotedGraph, (self.store, self.identifier))
1719
1720
1721 # Make sure QuotedGraph is ordered correctly
1722 # wrt to other Terms.
1723 # this must be done here, as the QuotedGraph cannot be
1724 # circularily imported in term.py
1725 rdflib.term._ORDERING[QuotedGraph]=11
1726
1727
1728 class Seq(object):
1729 """Wrapper around an RDF Seq resource
1730
1731 It implements a container type in Python with the order of the items
1732 returned corresponding to the Seq content. It is based on the natural
1733 ordering of the predicate names _1, _2, _3, etc, which is the
1734 'implementation' of a sequence in RDF terms.
1735 """
1736
1737 def __init__(self, graph, subject):
1738 """Parameters:
1739
1740 - graph:
1741 the graph containing the Seq
1742
1743 - subject:
1744 the subject of a Seq. Note that the init does not
1745 check whether this is a Seq, this is done in whoever
1746 creates this instance!
1747 """
1748
1749 _list = self._list = list()
1750 LI_INDEX = URIRef(str(RDF) + "_")
1751 for (p, o) in graph.predicate_objects(subject):
1752 if p.startswith(LI_INDEX): # != RDF.Seq: #
1753 i = int(p.replace(LI_INDEX, ''))
1754 _list.append((i, o))
1755
1756 # here is the trick: the predicates are _1, _2, _3, etc. Ie,
1757 # by sorting the keys (by integer) we have what we want!
1758 _list.sort()
1759
1760 def toPython(self):
1761 return self
1762
1763 def __iter__(self):
1764 """Generator over the items in the Seq"""
1765 for _, item in self._list:
1766 yield item
1767
1768 def __len__(self):
1769 """Length of the Seq"""
1770 return len(self._list)
1771
1772 def __getitem__(self, index):
1773 """Item given by index from the Seq"""
1774 index, item = self._list.__getitem__(index)
1775 return item
1776
1777
1778 class ModificationException(Exception):
1779
1780 def __init__(self):
1781 pass
1782
1783 def __str__(self):
1784 return ("Modifications and transactional operations not allowed on "
1785 "ReadOnlyGraphAggregate instances")
1786
1787
1788 class UnSupportedAggregateOperation(Exception):
1789
1790 def __init__(self):
1791 pass
1792
1793 def __str__(self):
1794 return ("This operation is not supported by ReadOnlyGraphAggregate "
1795 "instances")
1796
1797
1798 class ReadOnlyGraphAggregate(ConjunctiveGraph):
1799 """Utility class for treating a set of graphs as a single graph
1800
1801 Only read operations are supported (hence the name). Essentially a
1802 ConjunctiveGraph over an explicit subset of the entire store.
1803 """
1804
1805 def __init__(self, graphs, store='default'):
1806 if store is not None:
1807 super(ReadOnlyGraphAggregate, self).__init__(store)
1808 Graph.__init__(self, store)
1809 self.__namespace_manager = None
1810
1811 assert isinstance(graphs, list) \
1812 and graphs \
1813 and [g for g in graphs if isinstance(g, Graph)], \
1814 "graphs argument must be a list of Graphs!!"
1815 self.graphs = graphs
1816
1817 def __repr__(self):
1818 return "<ReadOnlyGraphAggregate: %s graphs>" % len(self.graphs)
1819
1820 def destroy(self, configuration):
1821 raise ModificationException()
1822
1823 # Transactional interfaces (optional)
1824 def commit(self):
1825 raise ModificationException()
1826
1827 def rollback(self):
1828 raise ModificationException()
1829
1830 def open(self, configuration, create=False):
1831 # TODO: is there a use case for this method?
1832 for graph in self.graphs:
1833 graph.open(self, configuration, create)
1834
1835 def close(self):
1836 for graph in self.graphs:
1837 graph.close()
1838
1839 def add(self, xxx_todo_changeme6):
1840 (s, p, o) = xxx_todo_changeme6
1841 raise ModificationException()
1842
1843 def addN(self, quads):
1844 raise ModificationException()
1845
1846 def remove(self, xxx_todo_changeme7):
1847 (s, p, o) = xxx_todo_changeme7
1848 raise ModificationException()
1849
1850 def triples(self, xxx_todo_changeme8):
1851 (s, p, o) = xxx_todo_changeme8
1852 for graph in self.graphs:
1853 if isinstance(p, Path):
1854 for s, o in p.eval(self, s, o):
1855 yield s, p, o
1856 else:
1857 for s1, p1, o1 in graph.triples((s, p, o)):
1858 yield (s1, p1, o1)
1859
1860 def __contains__(self, triple_or_quad):
1861 context = None
1862 if len(triple_or_quad) == 4:
1863 context = triple_or_quad[3]
1864 for graph in self.graphs:
1865 if context is None or graph.identifier == context.identifier:
1866 if triple_or_quad[:3] in graph:
1867 return True
1868 return False
1869
1870 def quads(self, xxx_todo_changeme9):
1871 """Iterate over all the quads in the entire aggregate graph"""
1872 (s, p, o) = xxx_todo_changeme9
1873 for graph in self.graphs:
1874 for s1, p1, o1 in graph.triples((s, p, o)):
1875 yield (s1, p1, o1, graph)
1876
1877 def __len__(self):
1878 return sum(len(g) for g in self.graphs)
1879
1880 def __hash__(self):
1881 raise UnSupportedAggregateOperation()
1882
1883 def __cmp__(self, other):
1884 if other is None:
1885 return -1
1886 elif isinstance(other, Graph):
1887 return -1
1888 elif isinstance(other, ReadOnlyGraphAggregate):
1889 return cmp(self.graphs, other.graphs)
1890 else:
1891 return -1
1892
1893 def __iadd__(self, other):
1894 raise ModificationException()
1895
1896 def __isub__(self, other):
1897 raise ModificationException()
1898
1899 # Conv. methods
1900
1901 def triples_choices(self, xxx_todo_changeme10, context=None):
1902 (subject, predicate, object_) = xxx_todo_changeme10
1903 for graph in self.graphs:
1904 choices = graph.triples_choices((subject, predicate, object_))
1905 for (s, p, o) in choices:
1906 yield (s, p, o)
1907
1908 def qname(self, uri):
1909 if hasattr(self, 'namespace_manager') and self.namespace_manager:
1910 return self.namespace_manager.qname(uri)
1911 raise UnSupportedAggregateOperation()
1912
1913 def compute_qname(self, uri, generate=True):
1914 if hasattr(self, 'namespace_manager') and self.namespace_manager:
1915 return self.namespace_manager.compute_qname(uri, generate)
1916 raise UnSupportedAggregateOperation()
1917
1918 def bind(self, prefix, namespace, override=True):
1919 raise UnSupportedAggregateOperation()
1920
1921 def namespaces(self):
1922 if hasattr(self, 'namespace_manager'):
1923 for prefix, namespace in self.namespace_manager.namespaces():
1924 yield prefix, namespace
1925 else:
1926 for graph in self.graphs:
1927 for prefix, namespace in graph.namespaces():
1928 yield prefix, namespace
1929
1930 def absolutize(self, uri, defrag=1):
1931 raise UnSupportedAggregateOperation()
1932
1933 def parse(self, source, publicID=None, format="xml", **args):
1934 raise ModificationException()
1935
1936 def n3(self):
1937 raise UnSupportedAggregateOperation()
1938
1939 def __reduce__(self):
1940 raise UnSupportedAggregateOperation()
1941
1942 def _assertnode(*terms):
1943 for t in terms:
1944 assert isinstance(t, Node), \
1945 'Term %s must be an rdflib term' % (t,)
1946 return True
1947
1948
1949 def test():
1950 import doctest
1951 doctest.testmod()
1952
1953 if __name__ == '__main__':
1954 test()