Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/rdflib/graph.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
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() |