summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Car <nicholas.car@surroundaustralia.com>2021-07-02 23:41:50 +1000
committerGitHub <noreply@github.com>2021-07-02 23:41:50 +1000
commit2bedfbb38eef48666fd386ced19f6442db9eb5d2 (patch)
treef4d86cf71c372d14026c091d53b99aedbde0853c
parent7cf5c38ead0c2d2ac590eb5ab72677250f52e6ab (diff)
parent4605bbe470c9ef39454cd36f16284e427e68df02 (diff)
downloadrdflib-2bedfbb38eef48666fd386ced19f6442db9eb5d2.tar.gz
Merge pull request #1347 from RDFLib/BerkeleyDB
Replace Sleepycat with BerkeleyDB
-rw-r--r--CHANGELOG.md12
-rw-r--r--docs/apidocs/examples.rst29
-rw-r--r--docs/docs.rst16
-rw-r--r--docs/gettingstarted.rst85
-rw-r--r--docs/index.rst47
-rw-r--r--docs/intro_to_creating_rdf.rst82
-rw-r--r--docs/intro_to_graphs.rst62
-rw-r--r--docs/intro_to_parsing.rst142
-rw-r--r--docs/intro_to_sparql.rst165
-rw-r--r--docs/persistence.rst29
-rw-r--r--docs/plugin_stores.rst2
-rw-r--r--docs/plugins.rst2
-rw-r--r--docs/upgrade5to6.rst58
-rw-r--r--docs/utilities.rst164
-rw-r--r--examples/berkeleydb_example.py134
-rw-r--r--examples/conjunctive_graphs.py50
-rw-r--r--examples/custom_datatype.py44
-rw-r--r--examples/custom_eval.py22
-rw-r--r--examples/film.py8
-rw-r--r--examples/foafpaths.py2
-rw-r--r--examples/graph_digest_benchmark.py167
-rw-r--r--examples/prepared_query.py12
-rw-r--r--examples/rdfa_example.py24
-rw-r--r--examples/resource.py45
-rw-r--r--examples/resource_example.py42
-rw-r--r--examples/simple_example.py10
-rw-r--r--examples/sleepycat_example.py57
-rw-r--r--rdflib/graph.py8
-rw-r--r--rdflib/plugin.py2
-rw-r--r--rdflib/plugins/stores/berkeleydb.py (renamed from rdflib/plugins/stores/sleepycat.py)44
-rw-r--r--rdflib/tools/rdfpipe.py4
-rw-r--r--requirements.dev.txt1
-rw-r--r--test/test_empty_xml_base.py4
-rw-r--r--test/test_issue160.py4
-rw-r--r--test/test_store_berkeleydb.py114
35 files changed, 1030 insertions, 663 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a72b4d66..678db1a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -978,7 +978,7 @@ Fixed a range of minor issues:
* http://code.google.com/p/rdflib/issues/detail?id=149
- Sleepycat Store broken with create=False
+ BerkeleyDB Store broken with create=False
* http://code.google.com/p/rdflib/issues/detail?id=134
@@ -1228,7 +1228,7 @@ Fixed conversion of (exiplicit) MySQL ports to integers.
Fixed MySQL store so it properly calculates ```__len__``` of
individual Graphs
-Aligned with how Sleepycat is generating events (remove events
+Aligned with how BerkeleyDB is generating events (remove events
are expressed in terms of interned strings)
Added code to catch unpickling related exceptions
@@ -1248,7 +1248,7 @@ TripleRemoved.
Added Journal Reader and Writer.
-Removed Sleepycat level journaling.
+Removed BerkeleyDB level journaling.
Added support for triple quoted Literal's.
@@ -1329,7 +1329,7 @@ argument to util's date_time method.
Fixed a relativize bug in the rdf/xml serializer.
Fixed NameError: global name 'URIRef' is not defined error in
-Sleepycat.py by adding missing import.
+BerkeleyDB.py by adding missing import.
Applied patch for Seq to sort list by integer, added by Drew
Hess.
@@ -1360,7 +1360,7 @@ Added N3 support to Graph and Store.
Added Sean's n3p parser, and ntriples parser.
-Sleepycat implementation has been revamped in the process of
+BerkeleyDB implementation has been revamped in the process of
expanding it to support the new requirements n3
requirements. It also now persists a journal -- more to come.
@@ -1390,7 +1390,7 @@ it provides Atomicity in the best case scenario.
2005/10/10 RELEASE 2.2.3
========================
-Fixed Sleepycat backend to commit after an add and
+Fixed BerkeleyDB backend to commit after an add and
remove. This should help just a bit with those unclean
shutdowns ;)
diff --git a/docs/apidocs/examples.rst b/docs/apidocs/examples.rst
index d099838f..84a9bee9 100644
--- a/docs/apidocs/examples.rst
+++ b/docs/apidocs/examples.rst
@@ -32,8 +32,7 @@ These examples all live in ``./examples`` in the source-distribution of RDFLib.
.. automodule:: examples.film
:members:
- :undoc-members:
- :show-inheritance:
+
:mod:`foafpaths` Module
-----------------------
@@ -51,34 +50,18 @@ These examples all live in ``./examples`` in the source-distribution of RDFLib.
:undoc-members:
:show-inheritance:
-:mod:`resource` Module
-----------------------
-
-.. automodule:: examples.resource
- :members:
- :undoc-members:
- :show-inheritance:
-
-:mod:`rdfa_example` Module
---------------------------
-
-.. automodule:: examples.rdfa_example
- :members:
- :undoc-members:
- :show-inheritance:
-
-:mod:`simple_example` Module
-----------------------------
+:mod:`resource_example` Module
+------------------------------
-.. automodule:: examples.simple_example
+.. automodule:: examples.resource_example
:members:
:undoc-members:
:show-inheritance:
-:mod:`sleepycat_example` Module
+:mod:`berkeleydb_example` Module
--------------------------------
-.. automodule:: examples.sleepycat_example
+.. automodule:: examples.berkeleydb_example
:members:
:undoc-members:
:show-inheritance:
diff --git a/docs/docs.rst b/docs/docs.rst
index c2a4a198..5f51f645 100644
--- a/docs/docs.rst
+++ b/docs/docs.rst
@@ -5,11 +5,11 @@ Writing RDFLib Documentation
================================
-The docs are generated with Sphinx.
+These docs are generated with Sphinx.
Sphinx makes it very easy to pull in doc-strings from modules,
classes, methods, etc. When writing doc-strings, special reST fields
-can be used to annotate parameters, return-types, etc. This make for
+can be used to annotate parameters, return-types, etc. This makes for
pretty API docs:
http://sphinx-doc.org/domains.html?highlight=param#info-field-lists
@@ -17,19 +17,21 @@ http://sphinx-doc.org/domains.html?highlight=param#info-field-lists
Building
--------
-To build you must have the `sphinx` package installed:
+To build you must have the ``sphinx`` package installed:
.. code-block:: bash
pip install sphinx
-Then you can do:
+See the documentation's full set of requirements in the ``sphinx-require,ens.txt`` file within the :file:`docs/` directory.
+
+ Once you have all the requirements installed you can run this command in the rdflib root directory:
.. code-block:: bash
python setup.py build_sphinx
-The docs will be generated in :file:`build/sphinx/html/`
+Docs will be generated in :file:`build/sphinx/html/` and API documentation, generated from doc-strings, will be placed in :file:`docs/apidocs/`.
API Docs
--------
@@ -40,8 +42,8 @@ API Docs are automatically generated with ``sphinx-apidoc``:
sphinx-apidoc -f -d 10 -o docs/apidocs/ rdflib examples
-(then ``rdflib.rst`` was tweaked manually to not include all
-convenience imports that are directly in the ``rdflib/__init__.py``)
+Note that ``rdflib.rst`` was manually tweaked so as to not include all
+ imports in ``rdflib/__init__.py``.
Tables
------
diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst
index a3637210..48e13d0f 100644
--- a/docs/gettingstarted.rst
+++ b/docs/gettingstarted.rst
@@ -8,7 +8,7 @@ Installation
============
RDFLib is open source and is maintained in a
-`GitHub <http://github.com/RDFLib/rdflib/>`_ repository. RDFLib releases, current and previous
+`GitHub <http://github.com/RDFLib/rdflib/>`_ repository. RDFLib releases, current and previous,
are listed on `PyPi <http://pypi.python.org/pypi/rdflib/>`_
The best way to install RDFLib is to use ``pip`` (sudo as required):
@@ -17,7 +17,13 @@ The best way to install RDFLib is to use ``pip`` (sudo as required):
$ pip install rdflib
-If you want the latest code to run, clone the master branch of the GitHub repo and use that.
+If you want the latest code to run, clone the master branch of the GitHub repo and use that or you can ``pip install``
+directly from GitHub:
+
+.. code-block :: bash
+
+ $ pip install git+https://github.com/RDFLib/rdflib.git@master#egg=rdflib
+
Support
=======
@@ -39,12 +45,12 @@ who hasn't worked with RDF before.*
The primary interface that RDFLib exposes for working with RDF is a
:class:`~rdflib.graph.Graph`.
-RDFLib graphs are not sorted containers; they have ordinary ``set``
+RDFLib graphs are un-sorted containers; they have ordinary ``set``
operations (e.g. :meth:`~rdflib.Graph.add` to add a triple) plus
methods that search triples and return them in arbitrary order.
RDFLib graphs also redefine certain built-in Python methods in order
-to behave in a predictable way; they `emulate container types
+to behave in a predictable way: they `emulate container types
<http://docs.python.org/release/2.5.2/ref/sequence-types.html>`_ and
are best thought of as a set of 3-item tuples ("triples", in RDF-speak):
@@ -57,44 +63,46 @@ are best thought of as a set of 3-item tuples ("triples", in RDF-speak):
(subjectN, predicateN, objectN)
]
-A tiny usage example:
+A tiny example
+==============
.. code-block:: python
- import rdflib
+ from rdflib import Graph
- # create a Graph
- g = rdflib.Graph()
+ # Create a Graph
+ g = Graph()
- # parse in an RDF file hosted on the Internet
- result = g.parse("http://www.w3.org/People/Berners-Lee/card")
+ # Parse in an RDF file hosted on the Internet
+ g.parse("http://www.w3.org/People/Berners-Lee/card")
- # loop through each triple in the graph (subj, pred, obj)
+ # Loop through each triple in the graph (subj, pred, obj)
for subj, pred, obj in g:
- # check if there is at least one triple in the Graph
+ # Check if there is at least one triple in the Graph
if (subj, pred, obj) not in g:
raise Exception("It better be!")
- # print the number of "triples" in the Graph
- print("graph has {} statements.".format(len(g)))
- # prints graph has 86 statements.
+ # Print the number of "triples" in the Graph
+ print(f"Graph g has {len(g)} statements.")
+ # Prints: Graph g has 86 statements.
- # print out the entire Graph in the RDF Turtle format
- print(g.serialize(format="turtle").decode("utf-8"))
+ # Print out the entire Graph in the RDF Turtle format
+ print(g.serialize(format="turtle"))
Here a :class:`~rdflib.graph.Graph` is created and then an RDF file online, Tim Berners-Lee's social network details, is
parsed into that graph. The ``print()`` statement uses the ``len()`` function to count the number of triples in the
graph.
-A more extensive example:
+A more extensive example
+========================
.. code-block:: python
from rdflib import Graph, Literal, RDF, URIRef
- # rdflib knows about some namespaces, like FOAF
+ # rdflib knows about quite a few popular namespaces, like W3C ontologies, schema.org etc.
from rdflib.namespace import FOAF , XSD
- # create a Graph
+ # Create a Graph
g = Graph()
# Create an RDF URI node to use as the subject for multiple triples
@@ -102,7 +110,7 @@ A more extensive example:
# Add triples using store's add() method.
g.add((donna, RDF.type, FOAF.Person))
- g.add((donna, FOAF.nick, Literal("donna", lang="ed")))
+ g.add((donna, FOAF.nick, Literal("donna", lang="en")))
g.add((donna, FOAF.name, Literal("Donna Fales")))
g.add((donna, FOAF.mbox, URIRef("mailto:donna@example.org")))
@@ -113,7 +121,7 @@ A more extensive example:
g.add((ed, RDF.type, FOAF.Person))
g.add((ed, FOAF.nick, Literal("ed", datatype=XSD.string)))
g.add((ed, FOAF.name, Literal("Edward Scissorhands")))
- g.add((ed, FOAF.mbox, URIRef("mailto:e.scissorhands@example.org")))
+ g.add((ed, FOAF.mbox, Literal("e.scissorhands@example.org", datatype=XSD.anyURI)))
# Iterate over triples in store and print them out.
print("--- printing raw triples ---")
@@ -131,7 +139,38 @@ A more extensive example:
# print all the data in the Notation3 format
print("--- printing mboxes ---")
- print(g.serialize(format='n3').decode("utf-8"))
+ print(g.serialize(format='n3'))
+
+
+A SPARQL query example
+======================
+
+.. code-block:: python
+
+ from rdflib import Graph
+
+ # Create a Graph, pare in Internet data
+ g = Graph().parse("http://www.w3.org/People/Berners-Lee/card")
+
+ # Query the data in g using SPARQL
+ # This query returns the 'name' of all ``foaf:Person`` instances
+ q = """
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+
+ SELECT ?name
+ WHERE {
+ ?p rdf:type foaf:Person .
+
+ ?p foaf:name ?name .
+ }
+ """
+
+ # Apply the query to the graph and iterate through results
+ for r in g.query(q):
+ print(r["name"])
+
+ # prints: Timothy Berners-Lee
+
More examples
diff --git a/docs/index.rst b/docs/index.rst
index a76ef159..94f97218 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -4,8 +4,7 @@
rdflib |release|
================
-RDFLib is a pure Python package for working with `RDF <http://www.w3.org/RDF/>`_. RDFLib contains useful APIs for
-working with RDF, including:
+RDFLib is a pure Python package for working with `RDF <http://www.w3.org/RDF/>`_. It contains:
* **Parsers & Serializers**
@@ -14,7 +13,7 @@ working with RDF, including:
* **Store implementations**
- * for in-memory and persistent RDF storage - Berkeley DB
+ * for in-memory and persistent RDF storage, including remote SPARQL endpoints
* **Graph interface**
@@ -24,7 +23,7 @@ working with RDF, including:
* **SPARQL 1.1 implementation**
- * supporting both Queries and Updates
+ * both Queries and Updates are supported
Getting started
@@ -45,7 +44,7 @@ If you have never used RDFLib, the following will help get you started:
In depth
--------
-If you are familiar with RDF and are looking for details on how RDFLib handles RDF, these are for you.
+If you are familiar with RDF and are looking for details on how RDFLib handles it, these are for you:
.. toctree::
:maxdepth: 1
@@ -54,6 +53,7 @@ If you are familiar with RDF and are looking for details on how RDFLib handles R
namespaces_and_bindings
persistence
merging
+ upgrade5to6
upgrade4to5
@@ -90,21 +90,44 @@ For developers
Developers might also like to join rdflib's dev mailing list: `<https://groups.google.com/group/rdflib-dev>`__
-The Code
---------
-The rdflib code is hosted on GitHub at `<https://github.com/RDFLib/rdflib>`__ where you lodge Issues and also Pull
-Requests to help improve this community project!
+Source Code
+-----------
+The rdflib source code is hosted on GitHub at `<https://github.com/RDFLib/rdflib>`__ where you can lodge Issues and
+create Pull Requests to help improve this community project!
The RDFlib organisation on GitHub at `<https://github.com/RDFLib>`__ maintains this package and a number of other RDF
-and related packaged that you might also find useful.
+and RDFlib-related packaged that you might also find useful.
Further help
------------
For asynchronous chat support, try our gitter channel at `<https://gitter.im/RDFLib/rdflib>`__
-If you would like more help with using rdflib, please post a question using the tag ``[rdflib]`` on StackOverflow. A list of
-existing ``[rdflib]`` tagged questions is there at:
+If you would like more help with using rdflib, rather than developing it, please post a question on StackOverflow using
+the tag ``[rdflib]``. A list of existing ``[rdflib]`` tagged questions is kept there at:
* `<https://stackoverflow.com/questions/tagged/rdflib>`__
+Glossary
+--------
+
+Here are a few RDF and Python terms referred to in this documentation. They are linked to wherever they occur.
+
+.. glossary::
+
+ functional property
+ Properties than can only occur once for a resource, i.e. for any relation (triple, in RDF) ``x p y``,
+ if ``p`` is functional, for any individual ``x``, there can be at most one individual ``y``.
+
+ OWL
+ The OWL 2 Web Ontology Language, informally OWL 2 or just OWL, is an ontology language for the Semantic Web
+ with formally defined meaning. OWL 2 ontologies provide classes, properties, individuals, and data values and
+ are stored as Semantic Web documents. OWL 2 ontologies can be used along with information written in RDF, and
+ OWL 2 ontologies themselves are primarily exchanged as RDF documents. See the `RDF 1.1 Concepts and Abstract
+ Syntax <https://www.w3.org/TR/rdf11-concepts/>`_ for more info.
+
+ RDF
+ The Resource Description Framework (RDF) is a framework for representing information in the Web. RDF data is
+ stored in graphs that are sets of subject-predicate-object triples, where the elements may be IRIs, blank nodes,
+ or datatyped literals. See the `OWL 2 Web Ontology Language
+ Document Overview <http://www.w3.org/TR/owl-overview/>`_ for more info. \ No newline at end of file
diff --git a/docs/intro_to_creating_rdf.rst b/docs/intro_to_creating_rdf.rst
index 238eafba..9409dfbe 100644
--- a/docs/intro_to_creating_rdf.rst
+++ b/docs/intro_to_creating_rdf.rst
@@ -11,10 +11,9 @@ RDF data is a graph where the nodes are URI references, Blank Nodes or Literals.
represented by the classes :class:`~rdflib.term.URIRef`, :class:`~rdflib.term.BNode`, and :class:`~rdflib.term.Literal`.
``URIRefs`` and ``BNodes`` can both be thought of as resources, such a person, a company, a website, etc.
-* A ``BNode`` is a node where the exact URI is not known.
-* A ``URIRef`` is a node where the exact URI is known. ``URIRef``\s are also used to represent the properties/predicates in the RDF graph.
-* ``Literals`` represent attribute values, such as a name, a date, a number, etc. The most common literal values are XML data types, e.g. string, int...
-
+* A ``BNode`` is a node where the exact URI is not known - usually a node with identity only in relation to other nodes.
+* A ``URIRef`` is a node where the exact URI is known. In addition to representing some subjects and predicates in RDF graphs, ``URIRef``\s are always used to represent properties/predicates
+* ``Literals`` represent object values, such as a name, a date, a number, etc. The most common literal values are XML data types, e.g. string, int... but custom types can be declared too
Nodes can be created by the constructors of the node classes:
@@ -25,23 +24,28 @@ Nodes can be created by the constructors of the node classes:
bob = URIRef("http://example.org/people/Bob")
linda = BNode() # a GUID is generated
- name = Literal('Bob') # passing a string
+ name = Literal("Bob") # passing a string
age = Literal(24) # passing a python int
height = Literal(76.5) # passing a python float
-Literals can be created from Python objects, this creates ``data-typed literals``, for the details on the mapping see :ref:`rdflibliterals`.
+Literals can be created from Python objects, this creates ``data-typed literals``. For the details on the mapping see
+:ref:`rdflibliterals`.
+
+For creating many ``URIRefs`` in the same ``namespace``, i.e. URIs with the same prefix, RDFLib has the
+:class:`rdflib.namespace.Namespace` class
-For creating many ``URIRefs`` in the same ``namespace``, i.e. URIs with the same prefix, RDFLib has the :class:`rdflib.namespace.Namespace` class::
+::
from rdflib import Namespace
n = Namespace("http://example.org/people/")
- n.bob # = rdflib.term.URIRef(u'http://example.org/people/bob')
- n.eve # = rdflib.term.URIRef(u'http://example.org/people/eve')
+ n.bob # == rdflib.term.URIRef("http://example.org/people/bob")
+ n.eve # == rdflib.term.URIRef("http://example.org/people/eve")
-
-This is very useful for schemas where all properties and classes have the same URI prefix. RDFLib defines Namespaces for some common RDF/OWL schemas, including most W3C ones:
+
+This is very useful for schemas where all properties and classes have the same URI prefix. RDFLib defines Namespaces for
+some common RDF/OWL schemas, including most W3C ones:
.. code-block:: python
@@ -50,68 +54,91 @@ This is very useful for schemas where all properties and classes have the same U
VOID, XMLNS, XSD
RDF.type
- # = rdflib.term.URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
+ # == rdflib.term.URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
FOAF.knows
- # = rdflib.term.URIRef("http://xmlns.com/foaf/0.1/knows")
+ # == rdflib.term.URIRef("http://xmlns.com/foaf/0.1/knows")
PROF.isProfileOf
- # = rdflib.term.URIRef("http://www.w3.org/ns/dx/prof/isProfileOf")
+ # == rdflib.term.URIRef("http://www.w3.org/ns/dx/prof/isProfileOf")
SOSA.Sensor
- # = rdflib.term.URIRef("http://www.w3.org/ns/sosa/Sensor")
+ # == rdflib.term.URIRef("http://www.w3.org/ns/sosa/Sensor")
-Adding Triples
---------------
+Adding Triples to a graph
+-------------------------
-We already saw in :doc:`intro_to_parsing`, how triples can be added from files and online locations with with the :meth:`~rdflib.graph.Graph.parse` function.
+We already saw in :doc:`intro_to_parsing`, how triples can be added from files and online locations with with the
+:meth:`~rdflib.graph.Graph.parse` function.
Triples can also be added within Python code directly, using the :meth:`~rdflib.graph.Graph.add` function:
.. automethod:: rdflib.graph.Graph.add
:noindex:
-:meth:`~rdflib.graph.Graph.add` takes a 3-tuple (a "triple") of RDFLib nodes. Try the following with the nodes and namespaces we defined previously:
+:meth:`~rdflib.graph.Graph.add` takes a 3-tuple (a "triple") of RDFLib nodes. Using the nodes and
+namespaces we defined previously:
.. code-block:: python
- from rdflib import Graph
+ from rdflib import Graph, URIRef, Literal, BNode
+ from rdflib.namespace import FOAF, RDF
+
g = Graph()
g.bind("foaf", FOAF)
+ bob = URIRef("http://example.org/people/Bob")
+ linda = BNode() # a GUID is generated
+
+ name = Literal("Bob")
+ age = Literal(24)
+
g.add((bob, RDF.type, FOAF.Person))
g.add((bob, FOAF.name, name))
+ g.add((bob, FOAF.age, age))
g.add((bob, FOAF.knows, linda))
g.add((linda, RDF.type, FOAF.Person))
g.add((linda, FOAF.name, Literal("Linda")))
- print(g.serialize(format="turtle").decode("utf-8"))
+ print(g.serialize())
+
outputs:
.. code-block:: Turtle
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<http://example.org/people/Bob> a foaf:Person ;
+ foaf:age 24 ;
foaf:knows [ a foaf:Person ;
foaf:name "Linda" ] ;
foaf:name "Bob" .
-For some properties, only one value per resource makes sense (i.e they are *functional properties*, or have max-cardinality of 1). The :meth:`~rdflib.graph.Graph.set` method is useful for this:
+For some properties, only one value per resource makes sense (i.e they are *functional properties*, or have a
+max-cardinality of 1). The :meth:`~rdflib.graph.Graph.set` method is useful for this:
.. code-block:: python
+ from rdflib import Graph, URIRef, Literal
+ from rdflib.namespace import FOAF
+
+ g = Graph()
+ bob = URIRef("http://example.org/people/Bob")
+
g.add((bob, FOAF.age, Literal(42)))
- print("Bob is ", g.value(bob, FOAF.age))
+ print(f"Bob is {g.value(bob, FOAF.age)}")
# prints: Bob is 42
g.set((bob, FOAF.age, Literal(43))) # replaces 42 set above
- print("Bob is now ", g.value(bob, FOAF.age))
+ print(f"Bob is now {g.value(bob, FOAF.age)}")
# prints: Bob is now 43
-:meth:`rdflib.graph.Graph.value` is the matching query method, it will return a single value for a property, optionally raising an exception if there are more.
+
+:meth:`rdflib.graph.Graph.value` is the matching query method. It will return a single value for a property, optionally
+raising an exception if there are more.
You can also add triples by combining entire graphs, see :ref:`graph-setops`.
@@ -124,7 +151,8 @@ Similarly, triples can be removed by a call to :meth:`~rdflib.graph.Graph.remove
.. automethod:: rdflib.graph.Graph.remove
:noindex:
-When removing, it is possible to leave parts of the triple unspecified (i.e. passing ``None``), this will remove all matching triples:
+When removing, it is possible to leave parts of the triple unspecified (i.e. passing ``None``), this will remove all
+matching triples:
.. code-block:: python
@@ -158,7 +186,7 @@ ensure the data actually aligns with other FOAF data, we could do this:
.. note:: Since rdflib 5.0.0, using ``foaf:member_name`` is somewhat prevented in RDFlib since FOAF is declared
as a :meth:`~rdflib.namespace.ClosedNamespace` class instance that has a closed set of members and
- ``foaf:member_name`` isn't one of them! If LiveJournal used RDFlib 5.0.0, an error would have been raised for
+ ``foaf:member_name`` isn't one of them! If LiveJournal had used RDFlib 5.0.0, an error would have been raised for
``foaf:member_name`` when the triple was created.
diff --git a/docs/intro_to_graphs.rst b/docs/intro_to_graphs.rst
index aa0fecf3..03bda55c 100644
--- a/docs/intro_to_graphs.rst
+++ b/docs/intro_to_graphs.rst
@@ -4,7 +4,8 @@
Navigating Graphs
=================
-An RDF Graph is a set of RDF triples, and we try to mirror exactly this in RDFLib. The Python :meth:`~rdflib.graph.Graph` tries to emulate a container type.
+An RDF Graph is a set of RDF triples, and we try to mirror exactly this in RDFLib. The Python
+:meth:`~rdflib.graph.Graph` tries to emulate a container type.
Graphs as Iterators
-------------------
@@ -13,19 +14,23 @@ RDFLib graphs override :meth:`~rdflib.graph.Graph.__iter__` in order to support
.. code-block:: python
- for subject, predicate, object in someGraph:
- if not (subject, predicate, object) in someGraph:
+ for s, p, o in someGraph:
+ if not (s, p, o) in someGraph:
raise Exception("Iterator / Container Protocols are Broken!!")
+This loop iterates through all the subjects(s), predicates (p) & objects (o) in ``someGraph``.
+
Contains check
--------------
-Graphs implement :meth:`~rdflib.graph.Graph.__contains__`, so you can check if a triple is in a graph with ``triple in graph`` syntax:
+Graphs implement :meth:`~rdflib.graph.Graph.__contains__`, so you can check if a triple is in a graph with a
+``triple in graph`` syntax:
.. code-block:: python
from rdflib import URIRef
from rdflib.namespace import RDF
+
bob = URIRef("http://example.org/people/bob")
if (bob, RDF.type, FOAF.Person) in graph:
print("This graph knows that Bob is a person!")
@@ -42,52 +47,63 @@ Note that this triple does not have to be completely bound:
Set Operations on RDFLib Graphs
-------------------------------
-Graphs override several pythons operators: :meth:`~rdflib.graph.Graph.__iadd__`, :meth:`~rdflib.graph.Graph.__isub__`, etc. This supports addition, subtraction and other set-operations on Graphs:
+Graphs override several pythons operators: :meth:`~rdflib.graph.Graph.__iadd__`, :meth:`~rdflib.graph.Graph.__isub__`,
+etc. This supports addition, subtraction and other set-operations on Graphs:
-============ ==================================================
+============ =============================================================
operation effect
-============ ==================================================
-``G1 + G2`` return new graph with union
-``G1 += G1`` in place union / addition
-``G1 - G2`` return new graph with difference
+============ =============================================================
+``G1 + G2`` return new graph with union (triples on both)
+``G1 += G2`` in place union / addition
+``G1 - G2`` return new graph with difference (triples in G1, not in G2)
``G1 -= G2`` in place difference / subtraction
``G1 & G2`` intersection (triples in both graphs)
``G1 ^ G2`` xor (triples in either G1 or G2, but not in both)
-============ ==================================================
+============ =============================================================
-.. warning:: Set-operations on graphs assume Blank Nodes are shared between graphs. This may or may not do what you want. See :doc:`merging` for details.
+.. warning:: Set-operations on graphs assume Blank Nodes are shared between graphs. This may or may not be what you want. See :doc:`merging` for details.
Basic Triple Matching
---------------------
-Instead of iterating through all triples, RDFLib graphs support basic triple pattern matching with a :meth:`~rdflib.graph.Graph.triples` function.
-This function is a generator of triples that match the pattern given by the arguments. The arguments of these are RDF terms that restrict the triples that are returned. Terms that are :data:`None` are treated as a wildcard. For example:
+Instead of iterating through all triples, RDFLib graphs support basic triple pattern matching with a
+:meth:`~rdflib.graph.Graph.triples` function. This function is a generator of triples that match a pattern given by
+arguments, i.e. arguments restrict the triples that are returned. Terms that are :data:`None` are treated as a wildcard.
+For example:
.. code-block:: python
- g.load("some_foaf.rdf")
+ g.load("some_foaf.ttl")
+ # find all subjects (s) of type (rdf:type) person (foaf:Person)
for s, p, o in g.triples((None, RDF.type, FOAF.Person)):
- print("{} is a person".format(s))
+ print(f"{s} is a person")
+ # find all subjects of any type
for s, p, o in g.triples((None, RDF.type, None)):
- print("{} is a {}".format(s, o))
+ print(f"{s} is a {o}")
+ # create a graph
bobgraph = Graph()
-
+ # add all triples with subject 'bob'
bobgraph += g.triples((bob, None, None))
-If you are not interested in whole triples, you can get only the bits you want with the methods :meth:`~rdflib.graph.Graph.objects`, :meth:`~rdflib.graph.Graph.subjects`, :meth:`~rdflib.graph.Graph.predicates`, :meth:`~rdflib.graph.Graph.predicate_objects`, etc. Each take parameters for the components of the triple to constraint:
+If you are not interested in whole triples, you can get only the bits you want with the methods
+:meth:`~rdflib.graph.Graph.objects`, :meth:`~rdflib.graph.Graph.subjects`, :meth:`~rdflib.graph.Graph.predicates`,
+:meth:`~rdflib.graph.Graph.predicate_objects`, etc. Each take parameters for the components of the triple to constraint:
.. code-block:: python
for person in g.subjects(RDF.type, FOAF.Person):
print("{} is a person".format(person))
-Finally, for some properties, only one value per resource makes sense (i.e they are *functional properties*, or have max-cardinality of 1). The :meth:`~rdflib.graph.Graph.value` method is useful for this, as it returns just a single node, not a generator:
+Finally, for some properties, only one value per resource makes sense (i.e they are *functional properties*, or have a
+max-cardinality of 1). The :meth:`~rdflib.graph.Graph.value` method is useful for this, as it returns just a single
+node, not a generator:
.. code-block:: python
- name = g.value(bob, FOAF.name) # get any name of bob
+ # get any name of bob
+ name = g.value(bob, FOAF.name)
# get the one person that knows bob and raise an exception if more are found
mbox = g.value(predicate = FOAF.name, object=bob, any=False)
@@ -97,10 +113,6 @@ Finally, for some properties, only one value per resource makes sense (i.e they
Here is a list of all convenience methods for querying Graphs:
-.. automethod:: rdflib.graph.Graph.label
- :noindex:
-.. automethod:: rdflib.graph.Graph.preferredLabel
- :noindex:
.. automethod:: rdflib.graph.Graph.triples
:noindex:
.. automethod:: rdflib.graph.Graph.value
diff --git a/docs/intro_to_parsing.rst b/docs/intro_to_parsing.rst
index 5b50e5ba..4460d30e 100644
--- a/docs/intro_to_parsing.rst
+++ b/docs/intro_to_parsing.rst
@@ -4,25 +4,26 @@
Loading and saving RDF
======================
-Reading an n-triples file
---------------------------
+Reading RDF files
+-----------------
-RDF data has various syntaxes (``xml``, ``n3``, ``ntriples``,
-``trix``, ``JSON-LD``, etc) that you might want to read. The simplest format is
-``ntriples``, a line-based format. Create the file :file:`demo.nt` in
-the current directory with these two lines:
+RDF data can be represented using various syntaxes (``turtle``, ``rdf/xml``, ``n3``, ``n-triples``,
+``trix``, ``JSON-LD``, etc.). The simplest format is
+``ntriples``, which is a triple-per-line format.
+
+Create the file :file:`demo.nt` in the current directory with these two lines in it:
.. code-block:: Turtle
- <http://bigasterisk.com/foaf.rdf#drewp> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
- <http://bigasterisk.com/foaf.rdf#drewp> <http://example.com/says> "Hello world" .
+ <http://example.com/drewp> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
+ <http://example.com/drewp> <http://example.com/says> "Hello World" .
+
+On line 1 this file says "drewp is a FOAF Person:. On line 2 it says "drep says "Hello World"".
-You need to tell RDFLib what format to parse, use the ``format``
-keyword-parameter to :meth:`~rdflib.graph.Graph.parse`, you can pass
-either a mime-type or the name (a :doc:`list of available parsers
-<plugin_parsers>` is available). If you are not sure what format your
-file will be, you can use :func:`rdflib.util.guess_format` which will
-guess based on the file extension.
+RDFLib can guess what format the file is by the file ending (".nt" is commonly used for n-triples) so you can just use
+:meth:`~rdflib.graph.Graph.parse` to read in the file. If the file had a non-standard RDF file ending, you could set the
+keyword-parameter ``format`` to specify either an Internet Media Type or the format name (a :doc:`list of available
+parsers <plugin_parsers>` is available).
In an interactive python interpreter, try this:
@@ -31,51 +32,112 @@ In an interactive python interpreter, try this:
from rdflib import Graph
g = Graph()
- g.parse("demo.nt", format="nt")
+ g.parse("demo.nt")
- print(len(g)) # prints 2
+ print(len(g))
+ # prints: 2
import pprint
for stmt in g:
pprint.pprint(stmt)
-
- # prints :
- (rdflib.term.URIRef('http://bigasterisk.com/foaf.rdf#drewp'),
- rdflib.term.URIRef('http://example.com/says'),
- rdflib.term.Literal('Hello world'))
- (rdflib.term.URIRef('http://bigasterisk.com/foaf.rdf#drewp'),
- rdflib.term.URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
- rdflib.term.URIRef('http://xmlns.com/foaf/0.1/Person'))
+ # prints:
+ # (rdflib.term.URIRef('http://example.com/drewp'),
+ # rdflib.term.URIRef('http://example.com/says'),
+ # rdflib.term.Literal('Hello World'))
+ # (rdflib.term.URIRef('http://example.com/drewp'),
+ # rdflib.term.URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
+ # rdflib.term.URIRef('http://xmlns.com/foaf/0.1/Person'))
The final lines show how RDFLib represents the two statements in the
-file. The statements themselves are just length-3 tuples; and the
-subjects, predicates, and objects are all rdflib types.
+file: the statements themselves are just length-3 tuples ("triples") and the
+subjects, predicates, and objects of the triples are all rdflib types.
-Reading remote graphs
----------------------
+Reading remote RDF
+------------------
-Reading graphs from the net is just as easy:
+Reading graphs from the Internet is easy:
.. code-block:: python
- g.parse("http://bigasterisk.com/foaf.rdf")
- print(len(g))
- # prints 42
+ from rdflib import Graph
-The format defaults to ``xml``, which is the common format for .rdf
-files you'll find on the net.
+ g = Graph()
+ g.parse("http://www.w3.org/People/Berners-Lee/card")
+ print(len(g))
+ # prints: 86
-RDFLib will also happily read RDF from any file-like object,
-i.e. anything with a ``.read()`` method.
+:func:`rdflib.Graph.parse` can process local files, remote data via a URL, as in this example, or RDF data in a string
+(using the ``data`` parameter).
Saving RDF
----------
-To store a graph in a file use the :func:`rdflib.Graph.serialize` function:
+To store a graph in a file, use the :func:`rdflib.Graph.serialize` function:
.. code-block:: python
- g.parse("http://bigasterisk.com/foaf.rdf")
- with open("foaf.n3", "wb") as f:
- g.serialize(f, format="n3") \ No newline at end of file
+ from rdflib import Graph
+
+ g = Graph()
+ g.parse("http://www.w3.org/People/Berners-Lee/card")
+ g.serialize(destination="tbl.ttl")
+
+This parses data from http://www.w3.org/People/Berners-Lee/card and stores it in a file ``tbl.ttl`` in this directory
+using the turtle format as a default.
+
+To read the same data and to save it in a varable ``v`` a string in the RDF/XML format, do this:
+
+.. code-block:: python
+
+ from rdflib import Graph
+
+ g = Graph()
+ g.parse("http://www.w3.org/People/Berners-Lee/card")
+ v = g.serialize(format="xml")
+
+
+Working with multi-graphs
+-------------------------
+
+To read and query multi-graphs, that is RDF data that is context-aware, you need to use rdflib's
+:class:`rdflib.ConjunctiveGraph` or :class:`rdflib.Dataset` class. These are extensions to :class:`rdflib.Graph` that
+know all about quads (triples + graph IDs).
+
+If you had this multi-graph data file (in the ``trig`` format, using new-style ``PREFIX`` statement (not the older
+``@prefix``):
+
+.. code-block:: Turtle
+
+ PREFIX eg: <http://example.com/person/>
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+
+ eg:graph-1 {
+ eg:drewp a foaf:Person .
+ eg:drewp eg:says "Hello World" .
+ }
+
+ eg:graph-2 {
+ eg:nick a foaf:Person .
+ eg:nick eg:says "Hi World" .
+ }
+
+You could parse the file and query it like this:
+
+.. code-block:: python
+
+ from rdflib import Dataset
+ from rdflib.namespace import RDF
+
+ g = Dataset()
+ g.parse("demo.trig")
+
+ for s, p, o, g in g.quads((None, RDF.type, None, None)):
+ print(s, g)
+
+This will print out:
+
+.. code-block::
+
+ http://example.com/person/drewp http://example.com/person/graph-1
+ http://example.com/person/nick http://example.com/person/graph-2
diff --git a/docs/intro_to_sparql.rst b/docs/intro_to_sparql.rst
index 8c7efd34..834231bf 100644
--- a/docs/intro_to_sparql.rst
+++ b/docs/intro_to_sparql.rst
@@ -10,12 +10,20 @@ Run a Query
The RDFLib comes with an implementation of the `SPARQL 1.1 Query
<http://www.w3.org/TR/sparql11-query/>`_ and `SPARQL 1.1 Update
-<http://www.w3.org/TR/sparql11-update/>`_ languages.
+<http://www.w3.org/TR/sparql11-update/>`_ query languages.
Queries can be evaluated against a graph with the
:meth:`rdflib.graph.Graph.query` method, and updates with
:meth:`rdflib.graph.Graph.update`.
+A query method returns a :class:`rdflib.query.Result` instance. For
+``SELECT`` queries, iterating over this returns
+:class:`rdflib.query.ResultRow` instances, each containing a set of
+variable bindings. For ``CONSTRUCT``/``DESCRIBE`` queries, iterating over the
+result object gives the triples. For ``ASK`` queries, iterating will yield
+the single boolean answer, or evaluating the result object in a
+boolean-context (i.e. ``bool(result)``)
+
The query method returns a :class:`rdflib.query.Result` instance. For
SELECT queries, iterating over this returns
:class:`rdflib.query.ResultRow` instances, each containing a set of
@@ -25,7 +33,7 @@ answer is obtained by iterating over the result object or by
evaluating the result object in a Boolean context
(i.e. ``bool(result)``).
-Consider the example...
+For example...
.. code-block:: python
@@ -41,70 +49,124 @@ Consider the example...
?b foaf:name ?bname .
}"""
- for row in g.query(knows_query):
- print("%s knows %s" % row)
+ for row in qres:
+ print(f"{row.aname} knows {row.bname}")
+
-The results are tuples of values in the same order as your SELECT
-arguments. The values can be accessed individually by variable
-name, either as attributes (``row.b``) or as items (``row["b"]``).
+
+The results are tuples of values in the same order as your ``SELECT``
+arguments. Alternatively, the values can be accessed by variable
+name, either as attributes, or as items, e.g. ``row.b`` and ``row["b"]`` are
+equivalent. The above, given the appropriate data, would print something like:
.. code-block:: text
- Dan Brickley knows Tim Berners-Lee
- Dan Brickley knows Dean Jackson
- Dan Brickley knows Mischa Tuffield
- Dan Brickley knows Ludovic Hirlimann
- Dan Brickley knows Libby Miller
+ Timothy Berners-Lee knows Edd Dumbill
+ Timothy Berners-Lee knows Jennifer Golbeck
+ Timothy Berners-Lee knows Nicholas Gibbins
...
-As an alternative to using ``PREFIX`` in the SPARQL query, namespace
-bindings can be passed in with the ``initNs`` kwarg (see
-:doc:`namespaces_and_bindings`):
+As an alternative to using ``SPARQL``\s ``PREFIX``, namespace
+bindings can be passed in with the ``initNs`` kwarg, see
+:doc:`namespaces_and_bindings`.
+
+Variables can also be pre-bound, using the ``initBindings`` kwarg which can
+pass in a ``dict`` of initial bindings. This is particularly
+useful for prepared queries, as described below.
+
+Update Queries
+^^^^^^^^^^^^^^
+
+Update queries are performed just like reading queries but using the :meth:`rdflib.graph.Graph.update` method. An
+example:
.. code-block:: python
- import rdflib
- from rdflib import FOAF
- g = rdflib.Graph()
- g.parse("http://danbri.org/foaf.rdf#")
-
- result = g.query(knows_query, initNs={ 'foaf': FOAF })
-
- for row in result:
- print(f"{row.aname} knows {row['bname']}")
-
-Variables can also be pre-bound, using ``initBindings`` kwarg can be
-used to pass in a ``dict`` of initial bindings, this is particularly
-useful for prepared queries, as described below.
+ from rdflib import Graph
+
+ # Create a Graph, add in some test data
+ g = Graph()
+ g.parse(
+ data="""
+ <x:> a <c:> .
+ <y:> a <c:> .
+ """,
+ format="turtle"
+ )
-Query a Remote Service
-^^^^^^^^^^^^^^^^^^^^^^
+ # Select all the things (s) that are of type (rdf:type) c:
+ qres = g.query("""SELECT ?s WHERE { ?s a <c:> }""")
+
+ for row in qres:
+ print(f"{row.s}")
+ # prints:
+ # x:
+ # y:
+
+ # Add in a new triple using SPATQL UPDATE
+ g.update("""INSERT DATA { <z:> a <c:> }""")
+
+ # Select all the things (s) that are of type (rdf:type) c:
+ qres = g.query("""SELECT ?s WHERE { ?s a <c:> }""")
+
+ print("After update:")
+ for row in qres:
+ print(f"{row.s}")
+ # prints:
+ # x:
+ # y:
+ # z:
+
+ # Change type of <y:> from <c:> to <d:>
+ g.update("""
+ DELETE { <y:> a <c:> }
+ INSERT { <y:> a <d:> }
+ WHERE { <y:> a <c:> }
+ """)
+ print("After second update:")
+ qres = g.query("""SELECT ?s ?o WHERE { ?s a ?o }""")
+ for row in qres:
+ print(f"{row.s} a {row.o}")
+ # prints:
+ # x: a c:
+ # z: a c:
+ # y: a d:
-The SERVICE keyword of SPARQL 1.1 can send a query to a remote SPARQL endpoint.
+
+Querying a Remote Service
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``SERVICE`` keyword of SPARQL 1.1 can send a query to a remote SPARQL endpoint.
.. code-block:: python
import rdflib
g = rdflib.Graph()
- qres = g.query('''
- SELECT ?s
- WHERE {
- SERVICE <http://dbpedia.org/sparql> {
- ?s <http://purl.org/linguistics/gold/hypernym> <http://dbpedia.org/resource/Leveller> .
- }
- } LIMIT 3''')
+ qres = g.query(
+ """
+ SELECT ?s
+ WHERE {
+ SERVICE <http://dbpedia.org/sparql> {
+ ?s a ?o .
+ }
+ }
+ LIMIT 3
+ """
+ )
+
for row in qres:
print(row.s)
-This example sends a query to `DBPedia
-<https://dbpedia.org/>`_'s SPARQL endpoint service so that it can run the query and then send back the result:
+
+This example sends a query to `DBPedia <https://dbpedia.org/>`_'s SPARQL endpoint service so that it can run the query
+and then send back the result:
.. code-block:: text
- http://dbpedia.org/resource/Elizabeth_Lilburne
- http://dbpedia.org/resource/Thomas_Prince_(Leveller)
- http://dbpedia.org/resource/John_Lilburne
+ <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.openlinksw.com/schemas/virtcxml#FacetCategoryPattern>
+ <http://www.w3.org/2001/XMLSchema#anyURI> <http://www.w3.org/2000/01/rdf-schema#Datatype>
+ <http://www.w3.org/2001/XMLSchema#anyURI> <http://www.w3.org/2000/01/rdf-schema#Datatype>
Prepared Queries
^^^^^^^^^^^^^^^^
@@ -122,17 +184,18 @@ initial bindings:
.. code-block:: python
- q = prepareQuery(
- 'SELECT ?s WHERE { ?person foaf:knows ?s .}',
- initNs = { "foaf": FOAF })
+ q = prepareQuery(
+ "SELECT ?s WHERE { ?person foaf:knows ?s .}",
+ initNs = { "foaf": FOAF }
+ )
- g = rdflib.Graph()
- g.load("foaf.rdf")
+ g = rdflib.Graph()
+ g.load("foaf.rdf")
- tim = rdflib.URIRef("http://www.w3.org/People/Berners-Lee/card#i")
+ tim = rdflib.URIRef("http://www.w3.org/People/Berners-Lee/card#i")
- for row in g.query(q, initBindings={'person': tim}):
- print row
+ for row in g.query(q, initBindings={'person': tim}):
+ print(row)
Custom Evaluation Functions
diff --git a/docs/persistence.rst b/docs/persistence.rst
index bd270a14..03f8d955 100644
--- a/docs/persistence.rst
+++ b/docs/persistence.rst
@@ -19,36 +19,43 @@ this API for a different store.
Stores currently shipped with core RDFLib
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* :class:`Memory <rdflib.plugins.stores.memory.Memory>` (not persistent!)
-* :class:`~rdflib.plugins.stores.sleepycat.Sleepycat` (on disk persistence via Python's :ref:`bsddb` or :ref:`bsddb3` packages)
-* :class:`~rdflib.plugins.stores.sparqlstore.SPARQLStore` - a read-only wrapper around a remote SPARQL Query endpoint.
-* :class:`~rdflib.plugins.stores.sparqlstore.SPARQLUpdateStore` - a read-write wrapper around a remote SPARQL query/update endpoint pair.
+* :class:`Memory <rdflib.plugins.stores.memory.Memory>` - not persistent!
+* :class:`~rdflib.plugins.stores.berkeleydb.BerkeleyDB` - on disk persistence via Python's `berkeleydb package <https://pypi.org/project/berkeleydb/>`_
+* :class:`~rdflib.plugins.stores.sparqlstore.SPARQLStore` - a read-only wrapper around a remote SPARQL Query endpoint
+* :class:`~rdflib.plugins.stores.sparqlstore.SPARQLUpdateStore` - a read-write wrapper around a remote SPARQL query/update endpoint pair
Usage
^^^^^
-Most cases passing the name of the store to the Graph constructor is enough:
+In most cases, passing the name of the store to the Graph constructor is enough:
.. code-block:: python
from rdflib import Graph
- graph = Graph(store='Sleepycat')
+ graph = Graph(store='BerkeleyDB')
Most store offering on-disk persistence will need to be opened before reading or writing.
-When peristing a triplestore (instead of a ConjuntiveGraph quadstore), you need to specify
+When persisting a triplestore (instead of a ConjuntiveGraph quadstore), you need to specify
an identifier with which you can open the graph:
.. code-block:: python
- graph = Graph('Sleepycat', identifier='mygraph')
+ graph = Graph('BerkeleyDB', identifier='mygraph')
# first time create the store:
- graph.open('/home/user/data/myRDFLibStore', create = True)
+ graph.open('/home/user/data/myRDFLibStore', create=True)
# work with the graph:
- graph.add( mytriples )
+ data = """
+ PREFIX : <https://example.org/>
+
+ :a :b :c .
+ :d :e :f .
+ :d :g :h .
+ """
+ graph.parse(data=data, format="ttl")
# when done!
graph.close()
@@ -70,5 +77,5 @@ More store implementations are available in RDFLib extension projects:
Example
^^^^^^^
-* :mod:`examples.sleepycat_example` contains an example for using a Sleepycat store.
+* :mod:`examples.berkeleydb_example` contains an example for using a BerkeleyDB store.
* :mod:`examples.sparqlstore_example` contains an example for using a SPARQLStore.
diff --git a/docs/plugin_stores.rst b/docs/plugin_stores.rst
index a936c54e..8fd511d3 100644
--- a/docs/plugin_stores.rst
+++ b/docs/plugin_stores.rst
@@ -14,6 +14,6 @@ SimpleMemory :class:`~rdflib.plugins.stores.memory.SimpleMemory`
Memory :class:`~rdflib.plugins.stores.memory.Memory`
SPARQLStore :class:`~rdflib.plugins.stores.sparqlstore.SPARQLStore`
SPARQLUpdateStore :class:`~rdflib.plugins.stores.sparqlstore.SPARQLUpdateStore`
-Sleepycat :class:`~rdflib.plugins.stores.sleepycat.Sleepycat`
+BerkeleyDB :class:`~rdflib.plugins.stores.berkeleydb.BerkeleyDB`
default :class:`~rdflib.plugins.stores.memory.Memory`
================= ============================================================
diff --git a/docs/plugins.rst b/docs/plugins.rst
index b4a4e1b4..fd3ef507 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -8,7 +8,7 @@ Plugins
:target: _static/plugins-diagram.svg
-Many parts of RDFLib are extensible with plugins through `setuptools entry-points <http://pythonhosted.org/distribute/setuptools.html#dynamic-discovery-of-services-and-plugins>`_. These pages list the plugins included in RDFLib core.
+Many parts of RDFLib are extensible with plugins, `see setuptools' 'Creating and discovering plugins' <https://packaging.python.org/guides/creating-and-discovering-plugins/>`_. These pages list the plugins included in RDFLib core.
diff --git a/docs/upgrade5to6.rst b/docs/upgrade5to6.rst
new file mode 100644
index 00000000..ecaf3631
--- /dev/null
+++ b/docs/upgrade5to6.rst
@@ -0,0 +1,58 @@
+.. _upgrade4to5: Upgrading from RDFLib version 5.0.0 to 6.0.0
+
+============================================
+Upgrading 5.0.0 to 6.0.0
+============================================
+
+6.0.0 fully adopts Python 3 practices and drops Python 2 support so it is neater, faster and generally more modern than
+5.0.0. It also tidies up the ``Graph`` API (removing duplicate functions) so it does include a few breaking changes.
+Additionally, there is a long list of PRs merged into 6.0.0 adding a number of small fixes and features which are listed
+below.
+
+RDFLib version 5.0.0 was released in 2020, 3 years after the previous version (4.2.2) and is fundamentally 5.0.0
+compatible with. If you need very long-term backwards-compatibility or Python 2 support, you need 5.0.0.
+
+
+Major Changes
+-------------
+
+
+All Changes
+-----------
+
+This list has been assembled from Pull Request and commit information.
+
+General Bugs Fixed:
+^^^^^^^^^^^^^^^^^^^
+* Pr 451 redux
+ `PR #978 <https://github.com/RDFLib/rdflib/pull/978>`_
+
+
+Enhanced Features:
+^^^^^^^^^^^^^^^^^^
+* Register additional serializer plugins for SPARQL mime types.
+ `PR #987 <https://github.com/RDFLib/rdflib/pull/987>`_
+
+
+SPARQL Fixes:
+^^^^^^^^^^^^^
+* Total order patch patch
+ `PR #862 <https://github.com/RDFLib/rdflib/pull/862>`_
+
+
+Code Quality and Cleanups:
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+* a slightly opinionated autopep8 run
+ `PR #870 <https://github.com/RDFLib/rdflib/pull/870>`_
+
+
+Testing:
+^^^^^^^^
+* 3.7 for travis
+ `PR #864 <https://github.com/RDFLib/rdflib/pull/864>`_
+
+
+Documentation Fixes:
+^^^^^^^^^^^^^^^^^^^^
+* Fix a doc string in the query module
+ `PR #976 <https://github.com/RDFLib/rdflib/pull/976>`_
diff --git a/docs/utilities.rst b/docs/utilities.rst
index 3074af7c..af6f1248 100644
--- a/docs/utilities.rst
+++ b/docs/utilities.rst
@@ -1,68 +1,103 @@
-Utilities and convenience functions
-===================================
+Utilities & convenience functions
+=================================
-For RDF programming, RDFLib and Python may not execute the fastest,
-but we try hard to make it the fastest and most convenient way to write!
+For RDF programming, RDFLib and Python may be the fastest tools,
+but we try hard to make them the easiest and most convenient to use and thus the *fastest* overall!
-This is a collection of hints and pointers for hassle free RDF-coding.
-
-User-friendly labels
---------------------
-
-Use :meth:`~rdflib.graph.Graph.label` to quickly look up the RDFS
-label of something, or better use
-:meth:`~rdflib.graph.Graph.preferredLabel` to find a label using
-several different properties (i.e. either ``rdfs:label``,
-``skos:preferredLabel``, ``dc:title``, etc.).
+This is a collection of hints and pointers for hassle-free RDF coding.
Functional properties
---------------------
Use :meth:`~rdflib.graph.Graph.value` and
:meth:`~rdflib.graph.Graph.set` to work with :term:`functional
-properties`, i.e. properties than can only occur once for a resource.
+property` instances, i.e. properties than can only occur once for a resource.
+
+.. code-block:: python
+
+ from rdflib import Graph, URIRef, Literal, BNode
+ from rdflib.namespace import FOAF, RDF
+
+ g = Graph()
+ g.bind("foaf", FOAF)
+
+ # Add demo data
+ bob = URIRef("http://example.org/people/Bob")
+ g.add((bob, RDF.type, FOAF.Person))
+ g.add((bob, FOAF.name, Literal("Bob")))
+ g.add((bob, FOAF.age, Literal(38)))
+
+ # To get a single value, use 'value'
+ print(g.value(bob, FOAF.age))
+ # prints: 38
+
+ # To change a single of value, use 'set'
+ g.set((bob, FOAF.age, Literal(39)))
+ print(g.value(bob, FOAF.age))
+ # prints: 39
+
Slicing graphs
--------------
Python allows slicing arrays with a ``slice`` object, a triple of
-``start``, ``stop`` index and step-size::
+``start``, ``stop`` and ``step-size``:
+
+.. code-block:: python
+
+ for i in range(20)[2:9:3]:
+ print(i)
+ # prints:
+ # 2, 5, 8
- >>> range(10)[2:9:3]
- [2, 5, 8]
RDFLib graphs override ``__getitem__`` and we pervert the slice triple
to be a RDF triple instead. This lets slice syntax be a shortcut for
:meth:`~rdflib.graph.Graph.triples`,
:meth:`~rdflib.graph.Graph.subject_predicates`,
-:meth:`~rdflib.graph.Graph.contains`, and other Graph query-methods::
+:meth:`~rdflib.graph.Graph.contains`, and other Graph query-methods:
+
+.. code-block:: python
+
+ from rdflib import Graph, URIRef, Literal, BNode
+ from rdflib.namespace import FOAF, RDF
+
+ g = Graph()
+ g.bind("foaf", FOAF)
+
+ # Add demo data
+ bob = URIRef("http://example.org/people/Bob")
+ bill = URIRef("http://example.org/people/Bill")
+ g.add((bob, RDF.type, FOAF.Person))
+ g.add((bob, FOAF.name, Literal("Bob")))
+ g.add((bob, FOAF.age, Literal(38)))
+ g.add((bob, FOAF.knows, bill))
- graph[:]
- # same as
- iter(graph)
+ print(g[:])
+ # same as
+ print(iter(g))
- graph[bob]
- # same as
- graph.predicate_objects(bob)
+ print(g[bob])
+ # same as
+ print(g.predicate_objects(bob))
- graph[bob : FOAF.knows]
- # same as
- graph.objects(bob, FOAF.knows)
-
- graph[bob : FOAF.knows : bill]
- # same as
- (bob, FOAF.knows, bill) in graph
+ print(g[bob: FOAF.knows])
+ # same as
+ print(g.objects(bob, FOAF.knows))
- graph[:FOAF.knows]
- # same as
- graph.subject_objects(FOAF.knows)
+ print(g[bob: FOAF.knows: bill])
+ # same as
+ print((bob, FOAF.knows, bill) in g)
+
+ print(g[:FOAF.knows])
+ # same as
+ print(g.subject_objects(FOAF.knows))
- ...
See :mod:`examples.slice` for a complete example.
-.. note:: Slicing is convenient for run-once scripts of playing around
- in the Python ``REPL``. However, since slicing returns
+.. note:: Slicing is convenient for run-once scripts for playing around
+ in the Python ``REPL``, however since slicing returns
tuples of varying length depending on which parts of the
slice are bound, you should be careful using it in more
complicated programs. If you pass in variables, and they are
@@ -81,36 +116,49 @@ Serializing a single term to N3
-------------------------------
For simple output, or simple serialisation, you often want a nice
-readable representation of a term. All terms have a
-``.n3(namespace_manager = None)`` method, which will return a suitable
-N3 format::
+readable representation of a term. All terms (URIRef, Literal etc.) have a
+``n3``, method, which will return a suitable N3 format:
+
+.. code-block:: python
+
+ from rdflib import Graph, URIRef, Literal
+ from rdflib.namespace import FOAF
- >>> from rdflib import Graph, URIRef, Literal, BNode
- >>> from rdflib.namespace import FOAF, NamespaceManager
+ # A URIRef
+ person = URIRef("http://xmlns.com/foaf/0.1/Person")
+ print(person.n3())
+ # prints: <http://xmlns.com/foaf/0.1/Person>
- >>> person = URIRef('http://xmlns.com/foaf/0.1/Person')
- >>> person.n3()
- u'<http://xmlns.com/foaf/0.1/Person>'
+ # Simplifying the output with a namespace prefix:
+ g = Graph()
+ g.bind("foaf", FOAF)
- >>> g = Graph()
- >>> g.bind("foaf", FOAF)
+ print(person.n3(g.namespace_manager))
+ # prints foaf:Person
- >>> person.n3(g.namespace_manager)
- u'foaf:Person'
+ # A typed literal
+ l = Literal(2)
+ print(l.n3())
+ # prints "2"^^<http://www.w3.org/2001/XMLSchema#integer>
- >>> l = Literal(2)
- >>> l.n3()
- u'"2"^^<http://www.w3.org/2001/XMLSchema#integer>'
-
- >>> l.n3(g.namespace_manager)
- u'"2"^^xsd:integer'
+ # Simplifying the output with a namespace prefix
+ # XSD is built in, so no need to bind() it!
+ l.n3(g.namespace_manager)
+ # prints: "2"^^xsd:integer
Parsing data from a string
--------------------------
-You can parse data from a string with the ``data`` param::
+You can parse data from a string with the ``data`` param:
+
+.. code-block:: python
+
+ from rdflib import Graph
- graph.parse(data = '<urn:a> <urn:p> <urn:b>.', format='n3')
+ g = Graph().parse(data="<a:> <p:> <p:>.")
+ for r in g.triples((None, None, None)):
+ print(r)
+ # prints: (rdflib.term.URIRef('a:'), rdflib.term.URIRef('p:'), rdflib.term.URIRef('p:'))
Commandline-tools
-----------------
diff --git a/examples/berkeleydb_example.py b/examples/berkeleydb_example.py
new file mode 100644
index 00000000..d50352b1
--- /dev/null
+++ b/examples/berkeleydb_example.py
@@ -0,0 +1,134 @@
+"""
+BerkeleyDB in use as a persistent Graph store.
+
+Example 1: simple actions
+
+* creating a ConjunctiveGraph using the BerkeleyDB Store
+* adding triples to it
+* counting them
+* closing the store, emptying the graph
+* re-opening the store using the same DB files
+* getting the same count of triples as before
+
+Example 2: larger data
+
+* loads multiple graphs downloaded from GitHub into a BerkeleyDB-baked graph stored in the folder gsq_vocabs.
+* does not delete the DB at the end so you can see it on disk
+"""
+import os
+from rdflib import ConjunctiveGraph, Namespace, Literal
+from rdflib.store import NO_STORE, VALID_STORE
+from tempfile import mktemp
+
+
+def example_1():
+ """Creates a ConjunctiveGraph and performs some BerkeleyDB tasks with it
+ """
+ path = mktemp()
+
+ # Declare we are using a BerkeleyDB Store
+ graph = ConjunctiveGraph("BerkeleyDB")
+
+ # Open previously created store, or create it if it doesn't exist yet
+ # (always doesn't exist in this example as using temp file location)
+ rt = graph.open(path, create=False)
+
+ if rt == NO_STORE:
+ # There is no underlying BerkeleyDB infrastructure, so create it
+ print("Creating new DB")
+ graph.open(path, create=True)
+ else:
+ print("Using existing DB")
+ assert rt == VALID_STORE, "The underlying store is corrupt"
+
+ print("Triples in graph before add:", len(graph))
+ print("(will always be 0 when using temp file for DB)")
+
+ # Now we'll add some triples to the graph & commit the changes
+ EG = Namespace("http://example.net/test/")
+ graph.bind("eg", EG)
+
+ graph.add((EG["pic:1"], EG.name, Literal("Jane & Bob")))
+ graph.add((EG["pic:2"], EG.name, Literal("Squirrel in Tree")))
+
+ graph.commit()
+
+ print("Triples in graph after add:", len(graph))
+ print("(should be 2)")
+
+ # display the graph in Turtle
+ print(graph.serialize())
+
+ # close when done, otherwise BerkeleyDB will leak lock entries.
+ graph.close()
+
+ graph = None
+
+ # reopen the graph
+ graph = ConjunctiveGraph("BerkeleyDB")
+
+ graph.open(path, create=False)
+
+ print("Triples still in graph:", len(graph))
+ print("(should still be 2)")
+
+ graph.close()
+
+ # Clean up the temp folder to remove the BerkeleyDB database files...
+ for f in os.listdir(path):
+ os.unlink(path + "/" + f)
+ os.rmdir(path)
+
+
+def example_2():
+ """Loads a number of SKOS vocabularies from GitHub into a BerkeleyDB-backed graph stored in the local folder
+ 'gsq_vocabs'
+
+ Should print out the number of triples after each load, e.g.:
+ 177
+ 248
+ 289
+ 379
+ 421
+ 628
+ 764
+ 813
+ 965
+ 1381
+ 9666
+ 9719
+ ...
+ """
+ from urllib.request import urlopen, Request
+ from urllib.error import HTTPError
+ import json
+ import base64
+
+ g = ConjunctiveGraph("BerkeleyDB")
+ g.open("gsg_vocabs", create=True)
+
+ # gsq_vocabs = "https://api.github.com/repos/geological-survey-of-queensland/vocabularies/git/trees/master"
+ gsq_vocabs = "https://api.github.com/repos/geological-survey-of-queensland/vocabularies/git/trees/cd7244d39337c1f4ef164b1cf1ea1f540a7277db"
+ try:
+ res = urlopen(Request(gsq_vocabs, headers={"Accept": "application/json"}))
+ except HTTPError as e:
+ return e.code, str(e), None
+
+ data = res.read()
+ encoding = res.info().get_content_charset('utf-8')
+ j = json.loads(data.decode(encoding))
+ for v in j["tree"]:
+ # process the element in GitHub result if it's a Turtle file
+ if v["path"].endswith(".ttl"):
+ # for each file, call it by URL, decode it and parse it into the graph
+ r = urlopen(v['url'])
+ content = json.loads(r.read().decode())["content"]
+ g.parse(data=base64.b64decode(content).decode(), format="turtle")
+ print(len(g))
+
+ print("loading complete")
+
+
+if __name__ == "__main__":
+ example_1()
+ example_2()
diff --git a/examples/conjunctive_graphs.py b/examples/conjunctive_graphs.py
index 4088419c..e6b6c11a 100644
--- a/examples/conjunctive_graphs.py
+++ b/examples/conjunctive_graphs.py
@@ -1,10 +1,10 @@
"""
-An RDFLib ConjunctiveGraph is an (unnamed) aggregation of all the named graphs
+An RDFLib ConjunctiveGraph is an (unnamed) aggregation of all the Named Graphs
within a Store. The :meth:`~rdflib.graph.ConjunctiveGraph.get_context`
-method can be used to get a particular named graph for use such as to add
-triples to, or the default graph can be used
+method can be used to get a particular named graph for use, such as to add
+triples to, or the default graph can be used.
-This example shows how to create named graphs and work with the
+This example shows how to create Named Graphs and work with the
conjunction (union) of all the graphs.
"""
@@ -14,7 +14,8 @@ from rdflib.plugins.stores.memory import Memory
if __name__ == "__main__":
- ns = Namespace("http://love.com#")
+ LOVE = Namespace("http://love.com#")
+ LOVERS = Namespace("http://love.com/lovers/")
mary = URIRef("http://love.com/lovers/mary")
john = URIRef("http://love.com/lovers/john")
@@ -25,35 +26,40 @@ if __name__ == "__main__":
store = Memory()
g = ConjunctiveGraph(store=store)
- g.bind("love", ns)
+ g.bind("love", LOVE)
+ g.bind("lovers", LOVERS)
- # add a graph for Mary's facts to the Conjunctive Graph
+ # Add a graph containing Mary's facts to the Conjunctive Graph
gmary = Graph(store=store, identifier=cmary)
- # Mary's graph only contains the URI of the person she love, not his cute name
- gmary.add((mary, ns["hasName"], Literal("Mary")))
- gmary.add((mary, ns["loves"], john))
+ # Mary's graph only contains the URI of the person she loves, not his cute name
+ gmary.add((mary, LOVE.hasName, Literal("Mary")))
+ gmary.add((mary, LOVE.loves, john))
- # add a graph for John's facts to the Conjunctive Graph
+ # Add a graph containing John's facts to the Conjunctive Graph
gjohn = Graph(store=store, identifier=cjohn)
# John's graph contains his cute name
- gjohn.add((john, ns["hasCuteName"], Literal("Johnny Boy")))
+ gjohn.add((john, LOVE.hasCuteName, Literal("Johnny Boy")))
- # enumerate contexts
+ # Enumerate contexts
+ print("Contexts:")
for c in g.contexts():
- print("-- %s " % c)
-
- # separate graphs
- print(gjohn.serialize(format="n3"))
+ print(f"-- {c.identifier} ")
+ print("===================")
+ # Separate graphs
+ print("John's Graph:")
+ print(gjohn.serialize())
print("===================")
- print(gmary.serialize(format="n3"))
+ print("Mary's Graph:")
+ print(gmary.serialize())
print("===================")
- # full graph
- print(g.serialize(format="n3"))
+ print("Full Graph")
+ print(g.serialize())
+ print("===================")
- # query the conjunction of all graphs
+ print("Query the conjunction of all graphs:")
xx = None
- for x in g[mary : ns.loves / ns.hasCuteName]: # type: ignore[misc]
+ for x in g[mary: LOVE.loves / LOVE.hasCuteName]:
xx = x
print("Q: Who does Mary love?")
print("A: Mary loves {}".format(xx))
diff --git a/examples/custom_datatype.py b/examples/custom_datatype.py
index 8d73e894..c3f43820 100644
--- a/examples/custom_datatype.py
+++ b/examples/custom_datatype.py
@@ -10,32 +10,40 @@ mappings between literal datatypes and Python objects
from rdflib import Graph, Literal, Namespace, XSD
-from rdflib.term import bind
+from rdflib import term
if __name__ == "__main__":
- # complex numbers are not registered by default
- # no custom constructor/serializer needed since
+ # Complex numbers are not registered by default
+ # No custom constructor/serializer needed since
# complex('(2+3j)') works fine
- bind(XSD.complexNumber, complex)
-
- ns = Namespace("urn:my:namespace:")
+ term.bind(XSD.complexNumber, complex)
+ # Create a complex number RDFlib Literal
+ EG = Namespace("http://example.com/")
c = complex(2, 3)
-
l = Literal(c)
+ # Add it to a graph
g = Graph()
- g.add((ns.mysubject, ns.myprop, l))
-
- n3 = g.serialize(format="n3")
-
- # round-trip through n3 serialize/parse
- g2 = Graph()
- g2.parse(data=n3, format="n3")
-
- l2 = list(g2)[0][2]
-
+ g.add((EG.mysubject, EG.myprop, l))
+ # Print the triple to see what it looks like
+ print(list(g)[0])
+ # prints: (
+ # rdflib.term.URIRef('http://example.com/mysubject'),
+ # rdflib.term.URIRef('http://example.com/myprop'),
+ # rdflib.term.Literal(
+ # '(2+3j)',
+ # datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#complexNumber')
+ # )
+ # )
+
+ # Round-trip through n3 serialize/parse
+ g2 = Graph().parse(data=g.serialize())
+
+ l2 = list(g2)[0]
print(l2)
- print(l2.value == c) # back to a python complex object
+ # Compare with the original python complex object (should be True)
+ # l2[2] is the object of the triple
+ print(l2[2].value == c)
diff --git a/examples/custom_eval.py b/examples/custom_eval.py
index 74c393a4..02bf0d7f 100644
--- a/examples/custom_eval.py
+++ b/examples/custom_eval.py
@@ -19,9 +19,9 @@ i.e. in your setup.py::
import rdflib
from rdflib.plugins.sparql.evaluate import evalBGP
-from rdflib.namespace import FOAF
+from rdflib.namespace import FOAF, RDF, RDFS
-inferredSubClass = rdflib.RDFS.subClassOf * "*" # any number of rdfs.subClassOf
+inferredSubClass = RDFS.subClassOf * "*" # any number of rdfs.subClassOf
def customEval(ctx, part):
@@ -30,11 +30,10 @@ def customEval(ctx, part):
"""
if part.name == "BGP":
-
# rewrite triples
triples = []
for t in part.triples:
- if t[1] == rdflib.RDF.type:
+ if t[1] == RDF.type:
bnode = rdflib.BNode()
triples.append((t[0], t[1], bnode))
triples.append((bnode, inferredSubClass, t[2]))
@@ -53,11 +52,20 @@ if __name__ == "__main__":
rdflib.plugins.sparql.CUSTOM_EVALS["exampleEval"] = customEval
g = rdflib.Graph()
- g.load("foaf.n3")
+ g.parse("foaf.n3")
# Add the subClassStmt so that we can query for it!
- g.add((FOAF.Person, rdflib.RDFS.subClassOf, FOAF.Agent))
+ g.add((FOAF.Person, RDFS.subClassOf, FOAF.Agent))
# Find all FOAF Agents
- for x in g.query("PREFIX foaf: <%s> SELECT * WHERE { ?s a foaf:Agent . }" % FOAF):
+ for x in g.query(
+ f"""
+ PREFIX foaf: <{FOAF}>
+
+ SELECT *
+ WHERE {{
+ ?s a foaf:Agent .
+ }}
+ """
+ ):
print(x)
diff --git a/examples/film.py b/examples/film.py
index bf2d5e0b..ae87cd7f 100644
--- a/examples/film.py
+++ b/examples/film.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python
"""
-
film.py: a simple tool to manage your movies review
Simon Rozet, http://atonie.org/
@@ -13,7 +12,6 @@ Requires download and import of Python imdb library from
https://imdbpy.github.io/ - (warning: installation
will trigger automatic installation of several other packages)
---
Usage:
film.py whoami "John Doe <john@doe.org>"
Initialize the store and set your name and email.
@@ -33,8 +31,8 @@ try:
except ImportError:
imdb = None
-from rdflib import BNode, ConjunctiveGraph, URIRef, Literal, Namespace, RDF
-from rdflib.namespace import FOAF, DC
+from rdflib import BNode, ConjunctiveGraph, URIRef, Literal, Namespace
+from rdflib.namespace import FOAF, DC, RDF
storefn = os.path.expanduser("~/movies.n3")
# storefn = '/home/simon/codes/film.dev/movies.n3'
@@ -53,7 +51,7 @@ class Store:
def __init__(self):
self.graph = ConjunctiveGraph()
if os.path.exists(storefn):
- self.graph.load(storeuri, format="n3")
+ self.graph.parse(storeuri)
self.graph.bind("dc", DC)
self.graph.bind("foaf", FOAF)
self.graph.bind("imdb", IMDB)
diff --git a/examples/foafpaths.py b/examples/foafpaths.py
index 127b7f5e..2941345e 100644
--- a/examples/foafpaths.py
+++ b/examples/foafpaths.py
@@ -32,7 +32,7 @@ from rdflib.namespace import FOAF
if __name__ == "__main__":
g = Graph()
- g.load("foaf.n3", format="n3")
+ g.parse("foaf.n3")
tim = URIRef("http://www.w3.org/People/Berners-Lee/card#i")
diff --git a/examples/graph_digest_benchmark.py b/examples/graph_digest_benchmark.py
deleted file mode 100644
index f5bfd02e..00000000
--- a/examples/graph_digest_benchmark.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python
-
-"""
-This benchmark will produce graph digests for all of the
-downloadable ontologies available in Bioportal.
-"""
-
-
-from rdflib import Namespace, Graph
-from rdflib.compare import to_isomorphic
-from six.moves.urllib.request import urlopen
-from six.moves import queue
-import sys
-import csv
-
-from io import StringIO
-from collections import defaultdict
-
-
-from multiprocessing import Process, Semaphore, Queue
-
-
-bioportal_query = """
-PREFIX metadata: <http://data.bioontology.org/metadata/>
-
-select distinct ?ontology ?title ?download where {
- ?ontology a metadata:Ontology;
- metadata:omvname ?title;
- metadata:links ?links.
- ?links metadata:Ontology ?download.
- filter(regex(?download, "/download"))
-}
-"""
-
-stat_cols = [
- "id",
- "ontology",
- "download_url",
- "tree_depth",
- "color_count",
- "individuations",
- "prunings",
- "initial_color_count",
- "adjacent_nodes",
- "initial_coloring_runtime",
- "triple_count",
- "graph_digest",
- "to_hash_runtime",
- "canonicalize_triples_runtime",
- "error",
-]
-
-
-def files_benchmark(ontologies, output_file, threads):
- w = open(output_file, "w")
- writer = csv.DictWriter(w, stat_cols)
- writer.writeheader()
- tasks = Queue()
- finished_tasks = Queue()
- dl_lock = Semaphore(4)
- task_count = len(ontologies)
-
- def worker(q, finished_tasks, dl_lock):
- try:
- while True:
- stats = q.get()
- og = Graph()
- try:
- og.load(stats["download_url"])
- print(stats["ontology"], stats["id"])
- ig = to_isomorphic(og)
- graph_digest = ig.graph_digest(stats)
- finished_tasks.put(stats)
- except Exception as e:
- print("ERROR", stats["id"], e)
- stats["error"] = str(e)
- finished_tasks.put(stats)
- except queue.Empty:
- pass
-
- for i in range(int(threads)):
- print("Starting worker", i)
- t = Process(target=worker, args=[tasks, finished_tasks, dl_lock])
- t.daemon = True
- t.start()
- for download in ontologies:
- stats = defaultdict(str)
- stats.update(
- {
- "id": download.split("/")[-1].split(".")[0],
- "ontology": download.split("/")[-1].split(".")[0],
- "download_url": download,
- }
- )
- tasks.put(stats)
- tasks.close()
- written_tasks = 0
- while written_tasks < task_count:
- stats = finished_tasks.get()
- # print "Writing", stats['ontology']
- writer.writerow(stats)
- w.flush()
- written_tasks += 1
-
-
-def bioportal_benchmark(apikey, output_file, threads):
- metadata = Namespace("http://data.bioontology.org/metadata/")
- url = "http://data.bioontology.org/ontologies?apikey=%s" % apikey
- ontology_graph = Graph()
- print(url)
- ontology_list_json = urlopen(url).read()
- ontology_graph.parse(StringIO(ontology_list_json), format="json-ld")
- ontologies = ontology_graph.query(bioportal_query)
- w = open(output_file, "w")
- writer = csv.DictWriter(w, stat_cols)
- writer.writeheader()
- tasks = Queue()
- finished_tasks = Queue()
- dl_lock = Semaphore(4)
- task_count = len(ontologies)
-
- def worker(q, finished_tasks, dl_lock):
- try:
- while True:
- stats = q.get()
- og = Graph()
- try:
- try:
- dl_lock.acquire()
- og.load(stats["download_url"] + "?apikey=%s" % apikey)
- finally:
- dl_lock.release()
- print(stats["ontology"], stats["id"])
- ig = to_isomorphic(og)
- graph_digest = ig.graph_digest(stats)
- finished_tasks.put(stats)
- except Exception as e:
- print("ERROR", stats["id"], e)
- stats["error"] = str(e)
- finished_tasks.put(stats)
- except:
- pass
-
- for i in range(int(threads)):
- print("Starting worker", i)
- t = Process(target=worker, args=[tasks, finished_tasks, dl_lock])
- t.daemon = True
- t.start()
- for ontology, title, download in ontologies:
- stats = defaultdict(str)
- stats.update({"id": ontology, "ontology": title, "download_url": download})
- tasks.put(stats)
- tasks.close()
- written_tasks = 0
- while written_tasks < task_count:
- stats = finished_tasks.get()
- # print "Writing", stats['ontology']
- writer.writerow(stats)
- w.flush()
- written_tasks += 1
-
-
-if __name__ == "__main__":
- if len(sys.argv) > 4:
- files_benchmark(sys.argv[1:-2], sys.argv[-2], sys.argv[-1])
- else:
- bioportal_benchmark(sys.argv[1], sys.argv[2], sys.argv[3])
diff --git a/examples/prepared_query.py b/examples/prepared_query.py
index 95912e59..daa424fa 100644
--- a/examples/prepared_query.py
+++ b/examples/prepared_query.py
@@ -2,24 +2,28 @@
SPARQL Queries be prepared (i.e parsed and translated to SPARQL algebra)
by the :meth:`rdflib.plugins.sparql.prepareQuery` method.
+``initNs`` can be used instead of PREFIX values.
+
When executing, variables can be bound with the
-``initBindings`` keyword parameter
+``initBindings`` keyword parameter.
"""
import rdflib
from rdflib.plugins.sparql import prepareQuery
from rdflib.namespace import FOAF
+
if __name__ == "__main__":
q = prepareQuery(
- "SELECT ?s WHERE { ?person foaf:knows ?s .}", initNs={"foaf": FOAF}
+ "SELECT ?name WHERE { ?person foaf:knows/foaf:name ?name . }",
+ initNs={"foaf": FOAF}
)
g = rdflib.Graph()
- g.load("foaf.n3", format="n3")
+ g.parse("foaf.n3")
tim = rdflib.URIRef("http://www.w3.org/People/Berners-Lee/card#i")
for row in g.query(q, initBindings={"person": tim}):
- print(row)
+ print(row.name)
diff --git a/examples/rdfa_example.py b/examples/rdfa_example.py
deleted file mode 100644
index 30462541..00000000
--- a/examples/rdfa_example.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-A simple example showing how to process RDFa from the web
-"""
-
-from rdflib import Graph
-
-if __name__ == "__main__":
- g = Graph()
-
- g.parse(
- "https://www.worldcat.org/title/library-of-babel/oclc/44089369", format="rdfa"
- )
-
- print("Books found:")
-
- for row in g.query(
- """SELECT ?title ?author WHERE {
- [ a schema:Book ;
- schema:author [ rdfs:label ?author ] ;
- schema:name ?title ]
- FILTER (LANG(?title) = 'en') } """
- ):
-
- print("%s by %s" % (row.title, row.author))
diff --git a/examples/resource.py b/examples/resource.py
deleted file mode 100644
index e82ad0b0..00000000
--- a/examples/resource.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-RDFLib has a :class:`~rdflib.resource.Resource` class, for a resource-centric API.
-
-A resource acts like a URIRef with an associated graph, and allows
-quickly adding or querying for triples where this resource is the
-subject.
-"""
-
-from rdflib import Graph, RDF, RDFS, Literal
-from rdflib.namespace import FOAF
-
-if __name__ == "__main__":
-
- g = Graph()
-
- bob = g.resource("urn:bob")
-
- bob.set(RDF.type, FOAF.Person) # .set replaces all other values
- bob.set(FOAF.name, Literal("Bob"))
-
- bill = g.resource("urn:bill")
-
- bill.add(RDF.type, FOAF.Person) # adds to existing values
- bill.add(RDF.type, FOAF.Agent)
- bill.set(RDFS.label, Literal("Bill"))
-
- bill.add(FOAF.knows, bob)
-
- # Resources returned when querying are 'auto-boxed' as resources:
-
- print("Bill's friend: ", bill.value(FOAF.knows).value(FOAF.name))
-
- # slicing ([] syntax) can also be used:
-
- print("Bill knows: ")
- for friend in bill[FOAF.knows]:
- print(next(friend[FOAF.name]))
-
- # or even quicker with paths:
- print("Bill knows: ")
- for friend in bill[FOAF.knows / FOAF.name]:
- print(friend)
-
- # setting single properties is also possible:
- bill[RDFS.label] = Literal("William")
diff --git a/examples/resource_example.py b/examples/resource_example.py
new file mode 100644
index 00000000..9085c32c
--- /dev/null
+++ b/examples/resource_example.py
@@ -0,0 +1,42 @@
+"""
+RDFLib has a :class:`~rdflib.resource.Resource` class, for a resource-centric API.
+The :class:`~rdflib.Graph` class also has a ``resource`` function that can be used
+to create resources and manipulate them by quickly adding or querying for triples
+where this resource is the subject.
+
+This example shows g.resource() in action.
+"""
+
+from rdflib import Graph, RDF, RDFS, Literal
+from rdflib.namespace import FOAF
+
+if __name__ == "__main__":
+ g = Graph()
+
+ # Create a Resource within graph g
+ bob = g.resource("http://example.com/bob")
+ # .set replaces all other values
+ bob.set(RDF.type, FOAF.Person)
+ bob.set(FOAF.name, Literal("Bob"))
+
+ bill = g.resource("http://example.com/bill")
+ # .add adds to existing values
+ bill.add(RDF.type, FOAF.Person)
+ bill.add(RDF.type, FOAF.Agent)
+ bill.set(RDFS.label, Literal("Bill"))
+
+ bill.add(FOAF.knows, bob)
+
+ # Resources returned when querying are 'auto-boxed' as resources:
+ print(f"Bill knows: {bill.value(FOAF.knows).value(FOAF.name)}")
+
+ # Slicing ([] syntax) can also be used:
+ for friend in bill[FOAF.knows]:
+ print(f"Bill knows: {next(friend[FOAF.name])}")
+
+ # Or even quicker with paths:
+ for friend in bill[FOAF.knows / FOAF.name]:
+ print(f"Bill knows: {friend}")
+
+ # Setting single properties is also possible:
+ bill[RDFS.label] = Literal("William")
diff --git a/examples/simple_example.py b/examples/simple_example.py
index 077382a3..49f08408 100644
--- a/examples/simple_example.py
+++ b/examples/simple_example.py
@@ -37,16 +37,16 @@ if __name__ == "__main__":
print()
print("RDF Serializations:")
+ # Serialize as Turtle (default)
+ print("--- start: turtle ---")
+ print(store.serialize())
+ print("--- end: turtle ---\n")
+
# Serialize as XML
print("--- start: rdf-xml ---")
print(store.serialize(format="pretty-xml"))
print("--- end: rdf-xml ---\n")
- # Serialize as Turtle
- print("--- start: turtle ---")
- print(store.serialize(format="turtle"))
- print("--- end: turtle ---\n")
-
# Serialize as NTriples
print("--- start: ntriples ---")
print(store.serialize(format="nt"))
diff --git a/examples/sleepycat_example.py b/examples/sleepycat_example.py
deleted file mode 100644
index 484484b9..00000000
--- a/examples/sleepycat_example.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-A simple example showing how to use a Sleepycat store to do on-disk
-persistence.
-"""
-
-from rdflib import ConjunctiveGraph, Namespace, Literal
-from rdflib.store import NO_STORE, VALID_STORE
-
-from tempfile import mktemp
-
-if __name__ == "__main__":
- path = mktemp()
-
- # Open previously created store, or create it if it doesn't exist yet
- graph = ConjunctiveGraph("Sleepycat")
-
- rt = graph.open(path, create=False)
-
- if rt == NO_STORE:
- # There is no underlying Sleepycat infrastructure, so create it
- graph.open(path, create=True)
- else:
- assert rt == VALID_STORE, "The underlying store is corrupt"
-
- print("Triples in graph before add: ", len(graph))
-
- # Now we'll add some triples to the graph & commit the changes
- rdflib = Namespace("http://rdflib.net/test/")
- graph.bind("test", "http://rdflib.net/test/")
-
- graph.add((rdflib["pic:1"], rdflib.name, Literal("Jane & Bob")))
- graph.add((rdflib["pic:2"], rdflib.name, Literal("Squirrel in Tree")))
-
- print("Triples in graph after add: ", len(graph))
-
- # display the graph in RDF/XML
- print(graph.serialize(format="n3"))
-
- # close when done, otherwise sleepycat will leak lock entries.
- graph.close()
-
- # reopen the graph
-
- graph = ConjunctiveGraph("Sleepycat")
-
- graph.open(path, create=False)
-
- print("Triples still in graph: ", len(graph))
-
- graph.close()
-
- # Clean up the temp folder to remove the Sleepycat database files...
- import os
-
- for f in os.listdir(path):
- os.unlink(path + "/" + f)
- os.rmdir(path)
diff --git a/rdflib/graph.py b/rdflib/graph.py
index b01d33f8..1b83b22e 100644
--- a/rdflib/graph.py
+++ b/rdflib/graph.py
@@ -1309,7 +1309,7 @@ class Graph(Node):
return processor.update(update_object, initBindings, initNs, **kwargs)
def n3(self):
- """return an n3 identifier for the Graph"""
+ """Return an n3 identifier for the Graph"""
return "[%s]" % self.identifier.n3()
def __reduce__(self):
@@ -1663,7 +1663,7 @@ class ConjunctiveGraph(Graph):
self,
source=None,
publicID=None,
- format="xml",
+ format=None,
location=None,
file=None,
data=None,
@@ -1847,7 +1847,7 @@ class Dataset(ConjunctiveGraph):
self,
source=None,
publicID=None,
- format="xml",
+ format=None,
location=None,
file=None,
data=None,
@@ -2149,7 +2149,7 @@ class ReadOnlyGraphAggregate(ConjunctiveGraph):
def absolutize(self, uri, defrag=1):
raise UnSupportedAggregateOperation()
- def parse(self, source, publicID=None, format="xml", **args):
+ def parse(self, source, publicID=None, format=None, **args):
raise ModificationException()
def n3(self):
diff --git a/rdflib/plugin.py b/rdflib/plugin.py
index 1e364e2c..87812504 100644
--- a/rdflib/plugin.py
+++ b/rdflib/plugin.py
@@ -138,7 +138,7 @@ register("Memory", Store, "rdflib.plugins.stores.memory", "Memory")
register("SimpleMemory", Store, "rdflib.plugins.stores.memory", "SimpleMemory")
register("Auditable", Store, "rdflib.plugins.stores.auditable", "AuditableStore")
register("Concurrent", Store, "rdflib.plugins.stores.concurrent", "ConcurrentStore")
-register("Sleepycat", Store, "rdflib.plugins.stores.sleepycat", "Sleepycat")
+register("BerkeleyDB", Store, "rdflib.plugins.stores.berkeleydb", "BerkeleyDB")
register("SPARQLStore", Store, "rdflib.plugins.stores.sparqlstore", "SPARQLStore")
register(
"SPARQLUpdateStore", Store, "rdflib.plugins.stores.sparqlstore", "SPARQLUpdateStore"
diff --git a/rdflib/plugins/stores/sleepycat.py b/rdflib/plugins/stores/berkeleydb.py
index b6b90470..d02a2158 100644
--- a/rdflib/plugins/stores/sleepycat.py
+++ b/rdflib/plugins/stores/berkeleydb.py
@@ -12,7 +12,7 @@ def bb(u):
try:
- from bsddb3 import db
+ from berkeleydb import db
has_bsddb = True
except ImportError:
@@ -33,10 +33,30 @@ if has_bsddb:
logger = logging.getLogger(__name__)
-__all__ = ["Sleepycat"]
+__all__ = ["BerkeleyDB"]
-class Sleepycat(Store):
+class BerkeleyDB(Store):
+ """\
+ A store that allows for on-disk persistent using BerkeleyDB, a fast
+ key/value DB.
+
+ This store implementation used to be known, previous to rdflib 6.0.0
+ as 'Sleepycat' due to that being the then name of the Python wrapper
+ for BerkeleyDB.
+
+ This store allows for quads as well as triples. See examples of use
+ in both the `examples.berkeleydb_example` and `test.test_store_berkeleydb`
+ files.
+
+ **NOTE on installation**:
+
+ To use this store, you must have BerkeleyDB installed on your system
+ separately to Python (`brew install berkeley-db` on a Mac) and also have
+ the BerkeleyDB Python wrapper installed (`pip install berkeleydb`).
+ You may need to install BerkeleyDB Python wrapper like this:
+ `YES_I_HAVE_THE_RIGHT_TO_USE_THIS_BERKELEY_DB_VERSION=1 pip install berkeleydb`
+ """
context_aware = True
formula_aware = True
transaction_aware = False
@@ -45,10 +65,10 @@ class Sleepycat(Store):
def __init__(self, configuration=None, identifier=None):
if not has_bsddb:
- raise ImportError("Unable to import bsddb/bsddb3, store is unusable.")
+ raise ImportError("Unable to import berkeleydb, store is unusable.")
self.__open = False
self.__identifier = identifier
- super(Sleepycat, self).__init__(configuration)
+ super(BerkeleyDB, self).__init__(configuration)
self._loads = self.node_pickler.loads
self._dumps = self.node_pickler.dumps
@@ -252,7 +272,7 @@ class Sleepycat(Store):
value = cspo.get(bb("%s^%s^%s^%s^" % (c, s, p, o)), txn=txn)
if value is None:
- self.__contexts.put(bb(c), "", txn=txn)
+ self.__contexts.put(bb(c), b"", txn=txn)
contexts_value = cspo.get(
bb("%s^%s^%s^%s^" % ("", s, p, o)), txn=txn
@@ -262,9 +282,9 @@ class Sleepycat(Store):
contexts_value = "^".encode("latin-1").join(contexts)
assert contexts_value is not None
- cspo.put(bb("%s^%s^%s^%s^" % (c, s, p, o)), "", txn=txn)
- cpos.put(bb("%s^%s^%s^%s^" % (c, p, o, s)), "", txn=txn)
- cosp.put(bb("%s^%s^%s^%s^" % (c, o, s, p)), "", txn=txn)
+ cspo.put(bb("%s^%s^%s^%s^" % (c, s, p, o)), b"", txn=txn)
+ cpos.put(bb("%s^%s^%s^%s^" % (c, p, o, s)), b"", txn=txn)
+ cosp.put(bb("%s^%s^%s^%s^" % (c, o, s, p)), b"", txn=txn)
if not quoted:
cspo.put(bb("%s^%s^%s^%s^" % ("", s, p, o)), contexts_value, txn=txn)
cpos.put(bb("%s^%s^%s^%s^" % ("", p, o, s)), contexts_value, txn=txn)
@@ -510,7 +530,7 @@ class Sleepycat(Store):
cursor.close()
def add_graph(self, graph):
- self.__contexts.put(bb(self._to_string(graph)), "")
+ self.__contexts.put(bb(self._to_string(graph)), b"")
def remove_graph(self, graph):
self.remove((None, None, None), graph)
@@ -523,14 +543,14 @@ class Sleepycat(Store):
k = self._dumps(term)
i = self.__k2i.get(k, txn=txn)
if i is None:
- # weird behavoir from bsddb not taking a txn as a keyword argument
+ # weird behaviour from bsddb not taking a txn as a keyword argument
# for append
if self.transaction_aware:
i = "%s" % self.__i2k.append(k, txn)
else:
i = "%s" % self.__i2k.append(k)
- self.__k2i.put(k, i, txn=txn)
+ self.__k2i.put(k, i.encode(), txn=txn)
else:
i = i.decode()
return i
diff --git a/rdflib/tools/rdfpipe.py b/rdflib/tools/rdfpipe.py
index d0673f72..98749487 100644
--- a/rdflib/tools/rdfpipe.py
+++ b/rdflib/tools/rdfpipe.py
@@ -187,8 +187,8 @@ def main():
logging.basicConfig(level=loglevel)
ns_bindings = {}
- if opts.ns:
- for ns_kw in opts.ns:
+ if opts.LOVE:
+ for ns_kw in opts.LOVE:
pfx, uri = ns_kw.split("=")
ns_bindings[pfx] = uri
diff --git a/requirements.dev.txt b/requirements.dev.txt
index 77fda028..09500eb1 100644
--- a/requirements.dev.txt
+++ b/requirements.dev.txt
@@ -5,3 +5,4 @@ nose-timer
coverage
flake8
doctest-ignore-unicode==0.1.2
+berkeleydb \ No newline at end of file
diff --git a/test/test_empty_xml_base.py b/test/test_empty_xml_base.py
index 2f3364b8..7410320b 100644
--- a/test/test_empty_xml_base.py
+++ b/test/test_empty_xml_base.py
@@ -43,7 +43,7 @@ baseUri2 = URIRef("http://example.com/foo/bar")
class TestEmptyBase(unittest.TestCase):
def setUp(self):
self.graph = ConjunctiveGraph()
- self.graph.parse(StringIO(test_data), publicID=baseUri)
+ self.graph.parse(StringIO(test_data), publicID=baseUri, format="xml")
def test_base_ref(self):
self.assertTrue(
@@ -58,7 +58,7 @@ class TestEmptyBase(unittest.TestCase):
class TestRelativeBase(unittest.TestCase):
def setUp(self):
self.graph = ConjunctiveGraph()
- self.graph.parse(StringIO(test_data2), publicID=baseUri2)
+ self.graph.parse(StringIO(test_data2), publicID=baseUri2, format="xml")
def test_base_ref(self):
self.assertTrue(
diff --git a/test/test_issue160.py b/test/test_issue160.py
index b3c7b422..67f56d83 100644
--- a/test/test_issue160.py
+++ b/test/test_issue160.py
@@ -52,9 +52,9 @@ class CollectionTest(TestCase):
# Fails: y a foo:Wrapper, foo:wraps x; x a rdf:List, a foo:Other ;
target1 = ConjunctiveGraph()
- target1.parse(data=target1xml)
+ target1.parse(data=target1xml, format="xml")
target2 = ConjunctiveGraph()
- target2.parse(data=target2xml)
+ target2.parse(data=target2xml, format="xml")
g = ConjunctiveGraph()
bits = [ex["a"], ex["b"], ex["c"]]
diff --git a/test/test_store_berkeleydb.py b/test/test_store_berkeleydb.py
new file mode 100644
index 00000000..634b7221
--- /dev/null
+++ b/test/test_store_berkeleydb.py
@@ -0,0 +1,114 @@
+import unittest
+from tempfile import mktemp
+from rdflib import ConjunctiveGraph, URIRef
+from rdflib.store import VALID_STORE
+
+
+class BerkeleyDBTestCase(unittest.TestCase):
+ def setUp(self):
+ self.store_name = "BerkeleyDB"
+ self.path = mktemp()
+ self.g = ConjunctiveGraph(store=self.store_name)
+ self.rt = self.g.open(self.path, create=True)
+ assert self.rt == VALID_STORE, "The underlying store is corrupt"
+ assert len(self.g) == 0, "There must be zero triples in the graph just after store (file) creation"
+ data = """
+ PREFIX : <https://example.org/>
+
+ :a :b :c .
+ :d :e :f .
+ :d :g :h .
+ """
+ self.g.parse(data=data, format="ttl")
+
+ def tearDown(self):
+ self.g.close()
+
+ def test_write(self):
+ assert len(self.g) == 3, "There must be three triples in the graph after the first data chunk parse"
+ data2 = """
+ PREFIX : <https://example.org/>
+
+ :d :i :j .
+ """
+ self.g.parse(data=data2, format="ttl")
+ assert len(self.g) == 4, "There must be four triples in the graph after the second data chunk parse"
+ data3 = """
+ PREFIX : <https://example.org/>
+
+ :d :i :j .
+ """
+ self.g.parse(data=data3, format="ttl")
+ assert len(self.g) == 4, "There must still be four triples in the graph after the thrd data chunk parse"
+
+ def test_read(self):
+ sx = None
+ for s in self.g.subjects(predicate=URIRef("https://example.org/e"), object=URIRef("https://example.org/f")):
+ sx = s
+ assert sx == URIRef("https://example.org/d")
+
+ def test_sparql_query(self):
+ q = """
+ PREFIX : <https://example.org/>
+
+ SELECT (COUNT(*) AS ?c)
+ WHERE {
+ :d ?p ?o .
+ }"""
+
+ c = 0
+ for row in self.g.query(q):
+ c = int(row.c)
+ assert c == 2, "SPARQL COUNT must return 2"
+
+ def test_sparql_insert(self):
+ q = """
+ PREFIX : <https://example.org/>
+
+ INSERT DATA {
+ :x :y :z .
+ }"""
+
+ self.g.update(q)
+ assert len(self.g) == 4, "After extra triple insert, length must be 4"
+
+ def test_multigraph(self):
+ q = """
+ PREFIX : <https://example.org/>
+
+ INSERT DATA {
+ GRAPH :m {
+ :x :y :z .
+ }
+ GRAPH :n {
+ :x :y :z .
+ }
+ }"""
+
+ self.g.update(q)
+
+ q = """
+ SELECT (COUNT(?g) AS ?c)
+ WHERE {
+ SELECT DISTINCT ?g
+ WHERE {
+ GRAPH ?g {
+ ?s ?p ?o
+ }
+ }
+ }
+ """
+ c = 0
+ for row in self.g.query(q):
+ c = int(row.c)
+ assert c == 3, "SPARQL COUNT must return 3 (default, :m & :n)"
+
+ def test_open_shut(self):
+ assert len(self.g) == 3, "Initially we must have 3 triples from setUp"
+ self.g.close()
+ self.g = None
+
+ # reopen the graph
+ self.g = ConjunctiveGraph("BerkeleyDB")
+ self.g.open(self.path, create=False)
+ assert len(self.g) == 3, "After close and reopen, we should still have the 3 originally added triples"