Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/rdflib/graph.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 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() |
