diff --git a/CHANGELOG.md b/CHANGELOG.md index 301e6a3e8..f16dd27db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,7 +54,6 @@ Merged PRs: * 2024-10-23 - build(deps-dev): bump ruff from 0.6.9 to 0.7.0 [PR #2942](https://github.com/RDFLib/rdflib/pull/2942) - ## 2024-10-17 RELEASE 7.1.0 This minor release incorporates just over 100 substantive PRs - interesting diff --git a/dataset_api.md b/dataset_api.md new file mode 100644 index 000000000..feb021529 --- /dev/null +++ b/dataset_api.md @@ -0,0 +1,158 @@ +Incorporate the changes proposed from Martynas, with the exception of graphs(), which would now return a dictionary of graph names (URIRef or BNode) to Graph objects (as the graph's identifier would be removed). + +``` +add add_named_graph(name: IdentifiedNode, graph: Graph) method +add has_named_graph(name: IdentifiedNode) method +add remove_named_graph(name: IdentifiedNode) method +add replace_named_graph(name: IdentifiedNode, graph: Graph)) method +add graphs() method as an alias for contexts() +add default_graph property as an alias for default_context +add get_named_graph as an alias for get_graph +deprecate graph(graph) method +deprecate remove_graph(graph) method +deprecate contexts() method +Using IdentifiedNode as a super-interface for URIRef and BNode (since both are allowed as graph names in RDF 1.1). +``` + +Make the following enhancements to the triples, quads, and subject/predicate/object APIs. + +Major changes: +P1. Remove `default_union` attribute and make the Dataset inclusive. +P2. Remove the Default Graph URI ("urn:x-rdflib:default"). +P3. Remove Graph class's "identifier" attribute to align with the W3C spec, impacting Dataset methods which use the Graph class. +P4. Make the graphs() method of Dataset return a dictionary of named graph names to Graph objects. +Enhancements: +P5. Support passing of iterables of Terms to triples, quads, and related methods, similar to the triples_choices method. +P6. Default the triples method to iterate with `(None, None, None)` + +With all of the above changes, including those changes proposed by Martynas, here are some examples: + +```python +from rdflib import Dataset, Graph, URIRef, Literal +from rdflib.namespace import RDFS + +# ============================================ +# Adding Data to the Dataset +# ============================================ + +# Initialize the dataset +d = Dataset() + +# Add a single triple to the Default Graph, and a single triple to a Named Graph +g1 = Graph() +g1.add( + ( + URIRef("http://example.com/subject-a"), + URIRef("http://example.com/predicate-a"), + Literal("Triple A") + ) +) +# merge with the default graph +d.default_graph += g1 +# or set the default graph +d.default_graph = g1 + +# Add a Graph to a Named Graph in the Dataset. +g2 = Graph() +g2.add( + ( + URIRef("http://example.com/subject-b"), + URIRef("http://example.com/predicate-b"), + Literal("Triple B") + ) +) +d.add_named_graph(name=URIRef("http://example.com/graph-B"), g2) + +# ============================================ +# Iterate over the entire Dataset returning triples +# ============================================ + +for triple in d.triples(): + print(triple) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A')) +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B')) + +# ============================================ +# Iterate over the entire Dataset returning quads +# ============================================ + +for quad in d.quads(): + print(quad) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A'), None) +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B'), rdflib.term.URIRef('http://example.com/graph-B')) + +# ============================================ +# Get the Default graph +# ============================================ + +dg = d.default_graph # same as current default_context + +# ============================================ +# Iterate on triples in the Default Graph only +# ============================================ + +for triple in d.triples(graph="default"): + print(triple) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A')) + +# ============================================ +# Access quads in Named Graphs only +# ============================================ + +for quad in d.quads(graph="named"): + print(quad) +# Output: +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B'), rdflib.term.URIRef('http://example.com/graph-B')) + +# ============================================ +# Equivalent to iterating over graphs() +# ============================================ + +for ng_name, ng_object in d.graphs().items(): + for quad in d.quads(graph=ng_name): + print(quad) +# Output: +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B'), rdflib.term.URIRef('http://example.com/graph-B')) + +# ============================================ +# Access triples in the Default Graph and specified Named Graphs. +# ============================================ + +for triple in d.triples(graph=["default", URIRef("http://example.com/graph-B")]): + print(triple) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A')) +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B')) + +# ============================================ +# Access quads in the Default Graph and specified Named Graphs. +# ============================================ + +for quad in d.quads(graph=["default", URIRef("http://example.com/graph-B")]): + print(quad) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A'), None) +(rdflib.term.URIRef('http://example.com/subject-b'), rdflib.term.URIRef('http://example.com/predicate-b'), rdflib.term.Literal('Triple B'), rdflib.term.URIRef('http://example.com/graph-B')) + +# ============================================ +# "Slice" the dataset on specified predicates. Same can be done on subjects, objects, graphs +# ============================================ + +filter_preds = [URIRef("http://example.com/predicate-a"), RDFS.label] +for quad in d.quads((None, filter_preds, None, None)): + print(quad) +# Output: +(rdflib.term.URIRef('http://example.com/subject-a'), rdflib.term.URIRef('http://example.com/predicate-a'), rdflib.term.Literal('Triple A'), None) + +# ============================================ +# Serialize the Dataset in a quads format. +# ============================================ + +print(d.serialize(format="nquads")) +# Output: + "Triple A" . + "Triple B" . +``` diff --git a/docs/apidocs/examples.rst b/docs/apidocs/examples.rst index 43b92c137..a8c3429bd 100644 --- a/docs/apidocs/examples.rst +++ b/docs/apidocs/examples.rst @@ -3,10 +3,18 @@ examples Package These examples all live in ``./examples`` in the source-distribution of RDFLib. -:mod:`~examples.conjunctive_graphs` Module ------------------------------------------- +:mod:`~examples.datasets` Module +-------------------------------- + +.. automodule:: examples.datasets + :members: + :undoc-members: + :show-inheritance: + +:mod:`~examples.jsonld_serialization` Module +-------------------------------------------- -.. automodule:: examples.conjunctive_graphs +.. automodule:: examples.jsonld_serialization :members: :undoc-members: :show-inheritance: diff --git a/docs/developers.rst b/docs/developers.rst index 5b2bb47cb..c951a373c 100644 --- a/docs/developers.rst +++ b/docs/developers.rst @@ -434,6 +434,8 @@ flag them as expecting to fail. Compatibility ------------- +RDFLib 8.x is likely to support only the Python versions in bugfix status at the time of its release, so perhaps 3.12+. + RDFlib 7.0.0 release and later only support Python 3.8.1 and newer. RDFlib 6.0.0 release and later only support Python 3.7 and newer. @@ -443,22 +445,46 @@ RDFLib 5.0.0 maintained compatibility with Python versions 2.7, 3.4, 3.5, 3.6, 3 Releasing --------- +These are the major steps for releasing new versions of RDFLib: + +#. Create a pre-release PR + + * that updates all the version numbers + * merge it with all tests passing + +#. Do the PyPI release +#. Do the GitHub release +#. Create a post-release PR + + * that updates all version numbers to next (alpha) release + * merge it with all tests passing + +#. Let the world know + + +1. Create a pre-release PR +~~~~~~~~~~~~~~~~~~~~~~~~~~ + Create a release-preparation pull request with the following changes: -* Updated version and date in ``CITATION.cff``. -* Updated copyright year in the ``LICENSE`` file. -* Updated copyright year in the ``docs/conf.py`` file. -* Updated main branch version and current version in the ``README.md`` file. -* Updated version in the ``pyproject.toml`` file. -* Updated ``__date__`` in the ``rdflib/__init__.py`` file. -* Accurate ``CHANGELOG.md`` entry for the release. +#. In ``pyproject.toml``, update the version number +#. In ``README.md``, update the *Versions & Releases* section +#. In ``rdflib/__init__.py``, update the ``__date__`` value +#. In ``docs/conf.py``, update copyright year +#. In ``CITATION.cff``, update the version and date +#. In ``LICENSE``, update the copyright year +#. In ``CHANGELOG.md``, write an entry for this release + #. You can use the tool ``admin/get_merged_prs.py`` to assist with compiling a log of PRs and commits since last release + +2. Do the PyPI release +~~~~~~~~~~~~~~~~~~~~~~ -Once the PR is merged, switch to the main branch, build the release and upload it to PyPI: +Once the pre-release PR is merged, switch to the main branch, build the release and upload it to PyPI: .. code-block:: bash # Clean up any previous builds - \rm -vf dist/* + rm -vf dist/* # Build artifacts poetry build @@ -487,24 +513,54 @@ Once the PR is merged, switch to the main branch, build the release and upload i ## poetry publish -u __token__ -p pypi- -Once this is done, create a release tag from `GitHub releases -`_. For a release of version -6.3.1 the tag should be ``6.3.1`` (without a "v" prefix), and the release title -should be "RDFLib 6.3.1". The release notes for the latest version be added to -the release description. The artifacts built with ``poetry build`` should be -uploaded to the release as release artifacts. +3. Do the GitHub release +~~~~~~~~~~~~~~~~~~~~~~~~ -The resulting release will be available at https://github.com/RDFLib/rdflib/releases/tag/6.3.1 +Once the PyPI release is done, tag the main branch with the version number of the release. For a release of version +6.3.1 the tag should be ``6.3.1`` (without a "v" prefix): + +.. code-block:: bash + + git tag 6.3.1 -Once this is done, announce the release at the following locations: -* Twitter: Just make a tweet from your own account linking to the latest release. -* RDFLib mailing list. -* RDFLib Gitter / matrix.org chat room. +Push this tag to GitHub: + +.. code-block:: bash + + git push --tags + + +Make a release from this tag at https://github.com/RDFLib/rdflib/releases/new + +The release title should be "{DATE} RELEASE {VERSION}". See previous releases at https://github.com/RDFLib/rdflib/releases + +The release notes should be just the same as the release info in ``CHANGELOG.md``, as authored in the first major step in this release process. + +The resulting release will be available at https://github.com/RDFLib/rdflib/releases/tag/6.3.1 + +4. Create a post-release PR +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once this is all done, create another post-release pull request with the following changes: -* Set the just released version in ``docker/latest/requirements.in`` and run - ``task docker:prepare`` to update the ``docker/latest/requirements.txt`` file. -* Set the version in the ``pyproject.toml`` file to the next minor release with - a ``a0`` suffix to indicate alpha 0. +#. In ``pyproject.toml``, update to the next minor release alpha + + * so a 6.3.1 release would have 6.1.4a0 as the next release alpha + +#. In ``docker/latest/requirements.in`` set the version to the just released version +#. Use ``task docker:prepare`` to update ``docker/latest/requirements.txt`` + + + +5. Let the world know +~~~~~~~~~~~~~~~~~~~~~ + +Announce the release at the following locations: + +* RDFLib mailing list +* RDFLib Gitter / matrix.org chat room +* Twitter: Just make a tweet from your own account linking to the latest release +* related mailing lists + * Jena: users@jena.apache.org + * W3C (currently RDF-Star WG): public-rdf-star@w3.org diff --git a/examples/datasets.py b/examples/datasets.py index 7dfc4e48b..4f2c845d4 100644 --- a/examples/datasets.py +++ b/examples/datasets.py @@ -1,8 +1,9 @@ """ -This file contains a number of common tasks using the RDFLib Dataset class. +This module contains a number of common tasks using the RDFLib Dataset class. -An RDFLib Dataset is an object that stores multiple Named Graphs - instances of RDFLib -Graph identified by IRI - within it and allows whole-of-dataset or single Graph use. +An RDFLib Dataset is an object that stores one Default Graph and zero or more Named +Graphs - all instances of RDFLib Graph identified by IRI - within it and allows +whole-of-dataset or single Graph use. Dataset extends Graph's Subject, Predicate, Object structure to include Graph - archaically called Context - producing quads of s, p, o, g. @@ -10,11 +11,11 @@ There is an older implementation of a Dataset-like class in RDFLib < 7.x called ConjunctiveGraph that is now deprecated. -Sections in this file: +Sections in this module: -1. Creating & Adding -2. Looping & Counting -3. Manipulating Graphs +1. Creating & Growing Datasets +2. Looping & Counting triples/quads in Datasets +3. Manipulating Graphs with Datasets """ from rdflib import Dataset, Graph, Literal, URIRef @@ -30,7 +31,7 @@ # mypy: ignore_errors=true ####################################################################################### -# 1. Creating & Adding +# 1. Creating & Growing ####################################################################################### # Create an empty Dataset @@ -43,9 +44,6 @@ # A string or a URIRef may be used, but safer to always use a URIRef for usage consistency graph_1_id = URIRef("http://example.com/graph-1") -# Add an empty Graph, identified by graph_1_id, to the Dataset -d.graph(identifier=graph_1_id) - # Add two quads to the Dataset which are triples + graph ID # These insert the triple into the GRaph specified by the ID d.add( @@ -69,8 +67,7 @@ # We now have 2 distinct quads in the Dataset to the Dataset has a length of 2 assert len(d) == 2 -# Add another quad to the Dataset specifying a non-existent Graph. -# The Graph is created automatically +# Add another quad to the Dataset. d.add( ( URIRef("http://example.com/subject-y"), @@ -82,10 +79,22 @@ assert len(d) == 3 +# Triples can be added to the Default Graph by not specifying a graph, or specifying the +# graph as None +d.add( + ( + URIRef("http://example.com/subject-dg"), + URIRef("http://example.com/predicate-dg"), + Literal("Triple DG"), + ) +) + +# Triples in the Default Graph contribute to the size/length of the Dataset +assert len(d) == 4 -# You can print the Dataset like you do a Graph but you must specify a quads format like -# 'trig' or 'trix', not 'turtle', unless the default_union parameter is set to True, and -# then you can print the entire Dataset in triples. +# You can print the Dataset like you do a Graph, in both "triples" and "quads" RDF +# mediatypes. +# Using trig, a "quads" or "graph aware" format: # print(d.serialize(format="trig").strip()) # you should see something like this: @@ -101,11 +110,27 @@ ex:graph-2 { ex:subject-y ex:predicate-y "Triple Y" . } + +{ + ex:subject-dg ex:predicate-dg "Triple DG" . +} +""" +# Using turtle, a "triples" format: +# print(d.serialize(format="turtle").strip()) + +# you should see something like this: +""" +@prefix ex: . + +ex:subject-x ex:predicate-x "Triple X" . +ex:subject-z ex:predicate-z "Triple Z" . +ex:subject-y ex:predicate-y "Triple Y" . +ex:subject-dg ex:predicate-dg "Triple DG" . """ # Print out one graph in the Dataset, using a standard Graph serialization format - longturtle -print(d.get_graph(URIRef("http://example.com/graph-2")).serialize(format="longturtle")) +print(d.get_named_graph(URIRef("http://example.com/graph-2")).serialize(format="longturtle")) # you should see something like this: """ @@ -122,7 +147,7 @@ ####################################################################################### # Loop through all quads in the dataset -for s, p, o, g in d.quads((None, None, None, None)): # type: ignore[arg-type] +for s, p, o, g in d.quads(): # type: ignore[arg-type] print(f"{s}, {p}, {o}, {g}") # you should see something like this: @@ -130,6 +155,7 @@ http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 http://example.com/subject-x, http://example.com/predicate-x, Triple X, http://example.com/graph-1 http://example.com/subject-y, http://example.com/predicate-y, Triple Y, http://example.com/graph-2 +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG", None """ # Loop through all the quads in one Graph - just constrain the Graph field @@ -142,6 +168,47 @@ http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 """ +# Or equivalently, use the "graph" parameter, similar to SPARQL Dataset clauses (FROM, +# FROM NAMED), to restrict the graphs. +for s, p, o, g in d.quads(graph=graph_1_id): # type: ignore[arg-type] + print(f"{s}, {p}, {o}, {g}") +# (Produces the same result as above) + +# To iterate through only the union of Named Graphs, you can use the special enum +# "named" with the graph parameter +for s, p, o, g in d.quads(graph=GraphEnum.named): # type: ignore[arg-type] + print(f"{s}, {p}, {o}, {g}") + +# you should see something like this: +""" +http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 +http://example.com/subject-x, http://example.com/predicate-x, Triple X, http://example.com/graph-1 +http://example.com/subject-y, http://example.com/predicate-y, Triple Y, http://example.com/graph-2 +""" + +# To iterate through the Default Graph, you can use the special enum "default" with +# the graph parameter +for s, p, o, g in d.quads(graph=GraphEnum.default): # type: ignore[arg-type] + print(f"{s}, {p}, {o}, {g}") + +# you should see something like this: +""" +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG" +""" + +# To iterate through multiple graphs, you can pass a list composed of Named Graph URIs +# and the special enums "named" and "default", for example to iterate through the +# default graph and a specified named graph: +for s, p, o, g in d.quads(graph=[graph_1_id, GraphEnum.default]): # type: ignore[arg-type] + print(f"{s}, {p}, {o}, {g}") + +# you should see something like this: +""" +http://example.com/subject-x, http://example.com/predicate-x, Triple X, http://example.com/graph-1 +http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG", None +""" + # Looping through triples in one Graph still works too for s, p, o in d.triples((None, None, None, graph_1_id)): # type: ignore[arg-type] print(f"{s}, {p}, {o}") @@ -152,12 +219,13 @@ http://example.com/subject-z, http://example.com/predicate-z, Triple Z """ -# Looping through triples across the whole Dataset will produce nothing -# unless we set the default_union parameter to True, since each triple is in a Named Graph +# Again, the "graph" parameter can be used +for s, p, o in d.triples(graph=graph_1_id): # type: ignore[arg-type] + print(f"{s}, {p}, {o}") + +# As the Dataset is inclusive, looping through triples across the whole Dataset will +# yield all triples in both the Default Graph and all Named Graphs. -# Setting the default_union parameter to True essentially presents all triples in all -# Graphs as a single Graph -d.default_union = True for s, p, o in d.triples((None, None, None)): print(f"{s}, {p}, {o}") @@ -166,17 +234,7 @@ http://example.com/subject-x, http://example.com/predicate-x, Triple X http://example.com/subject-z, http://example.com/predicate-z, Triple Z http://example.com/subject-y, http://example.com/predicate-y, Triple Y -""" - -# You can still loop through all quads now with the default_union parameter to True -for s, p, o, g in d.quads((None, None, None)): - print(f"{s}, {p}, {o}, {g}") - -# you should see something like this: -""" -http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 -http://example.com/subject-x, http://example.com/predicate-x, Triple X, http://example.com/graph-1 -http://example.com/subject-y, http://example.com/predicate-y, Triple Y, http://example.com/graph-2 +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG" """ # Adding a triple in graph-1 to graph-2 increases the number of distinct of quads in @@ -199,11 +257,11 @@ http://example.com/subject-y, http://example.com/predicate-y, Triple Y, http://example.com/graph-2 http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-2 +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG", None """ -# but the 'length' of the Dataset is still only 3 as only distinct triples are counted -assert len(d) == 3 - +# The 'length' of the Dataset is now five as triples and quads count towards the size/length of a Dataset. +assert len(d) == 5 # Looping through triples sees the 'Z' triple only once for s, p, o in d.triples((None, None, None)): @@ -214,64 +272,42 @@ http://example.com/subject-x, http://example.com/predicate-x, Triple X http://example.com/subject-z, http://example.com/predicate-z, Triple Z http://example.com/subject-y, http://example.com/predicate-y, Triple Y +http://example.com/subject-dg, http://example.com/predicate-dg, "Triple DG" """ ####################################################################################### # 3. Manipulating Graphs ####################################################################################### -# List all the Graphs in the Dataset -for x in d.graphs(): - print(x) +# List all the Graphs in the Dataset, as the Dataset's Named Graphs are a mapping from +# URIRefs to Graphs +for g_name, g_object in d.graphs().items(): + print(g_name, g_object) # this returns the graphs, something like: """ - a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'Memory']. - a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'Memory']. - a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'Memory']. +, a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'Memory']. +, a rdfg:Graph;rdflib:storage [a rdflib:Store;rdfs:label 'Memory']. """ # So try this -for x in d.graphs(): - print(x.identifier) +for g_name in d.graphs(): + print(g_name) -# you should see something like this, noting the default, currently empty, graph: +# you should see something like this: """ -urn:x-rdflib:default http://example.com/graph-2 http://example.com/graph-1 """ -# To add to the default Graph, just add a triple, not a quad, to the Dataset directly -d.add( - ( - URIRef("http://example.com/subject-n"), - URIRef("http://example.com/predicate-n"), - Literal("Triple N"), - ) -) -for s, p, o, g in d.quads((None, None, None, None)): - print(f"{s}, {p}, {o}, {g}") - -# you should see something like this, noting the triple in the default Graph: -""" -http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-1 -http://example.com/subject-z, http://example.com/predicate-z, Triple Z, http://example.com/graph-2 -http://example.com/subject-x, http://example.com/predicate-x, Triple X, http://example.com/graph-1 -http://example.com/subject-y, http://example.com/predicate-y, Triple Y, http://example.com/graph-2 -http://example.com/subject-n, http://example.com/predicate-n, Triple N, urn:x-rdflib:default -""" - # Loop through triples per graph -for x in d.graphs(): - print(x.identifier) - for s, p, o in x.triples((None, None, None)): +for g_name, g_object in d.graphs(): + print(g_name) + for s, p, o in g_object: print(f"\t{s}, {p}, {o}") # you should see something like this: """ -urn:x-rdflib:default - http://example.com/subject-n, http://example.com/predicate-n, Triple N http://example.com/graph-1 http://example.com/subject-x, http://example.com/predicate-x, Triple X http://example.com/subject-z, http://example.com/predicate-z, Triple Z @@ -280,25 +316,10 @@ http://example.com/subject-z, http://example.com/predicate-z, Triple Z """ -# The default_union parameter includes all triples in the Named Graphs and the Default Graph -for s, p, o in d.triples((None, None, None)): - print(f"{s}, {p}, {o}") - -# you should see something like this: -""" -http://example.com/subject-x, http://example.com/predicate-x, Triple X -http://example.com/subject-n, http://example.com/predicate-n, Triple N -http://example.com/subject-z, http://example.com/predicate-z, Triple Z -http://example.com/subject-y, http://example.com/predicate-y, Triple Y -""" - # To remove a graph -d.remove_graph(graph_1_id) - -# To remove the default graph -d.remove_graph(URIRef("urn:x-rdflib:default")) +d.remove_named_graph(name=graph_1_id) -# print what's left - one graph, graph-2 +# print what's left - one named graph, graph-2, and the default graph: print(d.serialize(format="trig")) # you should see something like this: @@ -310,30 +331,39 @@ ex:subject-z ex:predicate-z "Triple Z" . } + +{ + ex:subject-dg ex:predicate-dg "Triple DG" . +} """ -# To add a Graph that already exists, you must give it an Identifier or else it will be assigned a Blank Node ID -g_with_id = Graph(identifier=URIRef("http://example.com/graph-3")) -g_with_id.bind("ex", "http://example.com/") +# To replace a Graph that already exists, you can use the replace_named_graph method + +# Create a new Graph +g = Graph() -# Add a distinct triple to the exiting Graph, using Namepspace IRI shortcuts -# g_with_id.bind("ex", "http://example.com/") -g_with_id.add( +# Add a triple to the new Graph +g.add( ( URIRef("http://example.com/subject-k"), URIRef("http://example.com/predicate-k"), Literal("Triple K"), ) ) -d.add_graph(g_with_id) + +# Add the new Graph to the Dataset +d.replace_named_graph(graph=g, name=graph_1_id) + +# print the updated Dataset print(d.serialize(format="trig")) # you should see something like this: """ + @prefix ex: . -ex:graph-3 { - ex:subject_k ex:predicate_k "Triple K" . +ex:graph-1 { + ex:subject-k ex:predicate-k "Triple K" . } ex:graph-2 { @@ -341,12 +371,14 @@ ex:subject-z ex:predicate-z "Triple Z" . } + +{ + ex:subject-dg ex:predicate-dg "Triple DG" . +} """ # If you add a Graph with no specified identifier... g_no_id = Graph() -g_no_id.bind("ex", "http://example.com/") - g_no_id.add( ( URIRef("http://example.com/subject-l"), @@ -354,7 +386,7 @@ Literal("Triple L"), ) ) -d.add_graph(g_no_id) +d.add_named_graph(graph=g_no_id) # now when we print it, we will see a Graph with a Blank Node id: print(d.serialize(format="trig")) @@ -363,7 +395,7 @@ """ @prefix ex: . -ex:graph-3 { +ex:graph-1 { ex:subject-k ex:predicate-k "Triple K" . } @@ -376,4 +408,23 @@ _:N9cc8b54c91724e31896da5ce41e0c937 { ex:subject-l ex:predicate-l "Triple L" . } + +{ + ex:subject-dg ex:predicate-dg "Triple DG" . +} +""" + +# triples, quads, subjects, predicates, objects, subject_predicates, predicate_objects, +# subject_objects methods support passing a list of values for their parameters, for +# example: + +# "Slice" the dataset on specified predicates. +filter_preds = [URIRef("http://example.com/predicate-k"), + URIRef("http://example.com/predicate-y")] +for s, o in d.subject_objects(filter_preds): + print(f"{s}, {o}") +# you should see something like this: +""" +http://example.com/subject-k, Triple K +http://example.com/subject-y, Triple Y """ diff --git a/examples/jsonld_serialization.py b/examples/jsonld_serialization.py index 5bee1a614..dd83d6a5d 100644 --- a/examples/jsonld_serialization.py +++ b/examples/jsonld_serialization.py @@ -1,24 +1,35 @@ """ -JSON-LD is "A JSON-based Serialization for Linked Data" (https://www.w3.org/TR/json-ld/) that RDFLib implements for RDF serialization. +JSON-LD is "A JSON-based Serialization for Linked Data" (https://www.w3.org/TR/json-ld/) +that RDFLib implements for RDF serialization. -This file demonstrated some of the JSON-LD things you can do with RDFLib. Parsing & serializing so far. More to be added later. +This file demonstrated some of the JSON-LD things you can do with RDFLib. Parsing & +serializing so far. More to be added later. Parsing ------- -There are a number of "flavours" of JSON-LD - compact and verbose etc. RDFLib can parse all of these in a normal RDFLib way. + +There are a number of "flavours" of JSON-LD - compact and verbose etc. RDFLib can parse +all of these in a normal RDFLib way. Serialization ------------- -JSON-LD has a number of options for serialization - more than other RDF formats. For example, IRIs within JSON-LD can be compacted down to CURIES when a "context" statment is added to the JSON-LD data that maps identifiers - short codes - to IRIs and namespace IRIs like this: -# here the short code "dcterms" is mapped to the IRI http://purl.org/dc/terms/ and "schema" to https://schema.org/, as per RDFLib's in-build namespace prefixes +JSON-LD has a number of options for serialization - more than other RDF formats. For +example, IRIs within JSON-LD can be compacted down to CURIES when a "context" statement +is added to the JSON-LD data that maps identifiers - short codes - to IRIs and namespace +IRIs like this: -"@context": { - "dct": "http://purl.org/dc/terms/", - "schema": "https://schema.org/" -} +.. code-block:: json + + "@context": { + "dcterms": "http://purl.org/dc/terms/", + "schema": "https://schema.org/" + } + +Here the short code "dcterms" is mapped to the IRI http://purl.org/dc/terms/ and +"schema" to https://schema.org/, as per RDFLib's in-build namespace prefixes. """ # import RDFLib and other things diff --git a/rdflib/__init__.py b/rdflib/__init__.py index 934da5f47..21a5c4705 100644 --- a/rdflib/__init__.py +++ b/rdflib/__init__.py @@ -166,7 +166,8 @@ """ -from rdflib.graph import ConjunctiveGraph, Dataset, Graph +from rdflib.graph import Graph +from rdflib.dataset import Dataset from rdflib.namespace import ( BRICK, CSVW, diff --git a/rdflib/compare.py b/rdflib/compare.py index 58644ae8f..325ee392e 100644 --- a/rdflib/compare.py +++ b/rdflib/compare.py @@ -95,7 +95,7 @@ from hashlib import sha256 from typing import TYPE_CHECKING, Optional, Union -from rdflib.graph import ConjunctiveGraph, Graph, ReadOnlyGraphAggregate, _TripleType +from rdflib.graph import Graph, _TripleType from rdflib.term import BNode, IdentifiedNode, Node, URIRef if TYPE_CHECKING: @@ -147,7 +147,7 @@ def wrapped_f(*args, **kwargs): return wrapped_f -class IsomorphicGraph(ConjunctiveGraph): +class IsomorphicGraph(Graph): """An implementation of the RGDA1 graph digest algorithm. An implementation of RGDA1 (publication below), @@ -569,7 +569,7 @@ def isomorphic(graph1: Graph, graph2: Graph) -> bool: return gd1 == gd2 -def to_canonical_graph(g1: Graph, stats: Stats | None = None) -> ReadOnlyGraphAggregate: +def to_canonical_graph(g1: Graph, stats: Stats | None = None) -> Graph: """Creates a canonical, read-only graph. Creates a canonical, read-only graph where all bnode id:s are based on @@ -577,7 +577,7 @@ def to_canonical_graph(g1: Graph, stats: Stats | None = None) -> ReadOnlyGraphAg """ graph = Graph() graph += _TripleCanonicalizer(g1).canonical_triples(stats=stats) - return ReadOnlyGraphAggregate([graph]) + return graph def graph_diff(g1: Graph, g2: Graph) -> tuple[Graph, Graph, Graph]: diff --git a/rdflib/dataset.py b/rdflib/dataset.py new file mode 100644 index 000000000..0d2f94e63 --- /dev/null +++ b/rdflib/dataset.py @@ -0,0 +1,330 @@ +from __future__ import annotations + +import pathlib +from enum import Enum +from typing import ( + IO, + TYPE_CHECKING, + Any, + BinaryIO, + TextIO, List, Union, Optional, Tuple, Set, Callable, Type, +) + +from rdflib import plugin +from rdflib.collection import Collection +from rdflib.namespace import NamespaceManager +from rdflib.parser import InputSource +from rdflib.resource import Resource +from rdflib.store import Store +from rdflib.term import ( + BNode, + Literal, + URIRef, IdentifiedNode, Node, +) + +if TYPE_CHECKING: + from collections.abc import Generator + +from rdflib.graph import _TripleType, \ + Graph, _TripleSliceType, _QuadSliceType, _QuadType, _PredicateSliceType, \ + _ObjectSliceType, _SubjectSliceType + +from rdflib._type_checking import _NamespaceSetString + + +class GraphType(Enum): + DEFAULT = "default" + NAMED = "named" + +_GraphSliceType = List[Union[GraphType, IdentifiedNode]] | GraphType | IdentifiedNode + + +class Dataset(): + """ + #TODO update docstring. For reference see: + - Temporary writeup in /dataset_api.md, and + - examples/datasets.py + """ + + def __init__( + self, + store: Store | str = "default", + namespace_manager: NamespaceManager | None = None, + base: str | None = None, + bind_namespaces: _NamespaceSetString = "rdflib", + ): + super(Dataset, self).__init__(store=store, identifier=None) + self.base = base + self.__store: Store + if not isinstance(store, Store): + # TODO: error handling + self.__store = store = plugin.get(store, Store)() + else: + self.__store = store + self.__namespace_manager = namespace_manager + self._bind_namespaces = bind_namespaces + self.context_aware = True + self.formula_aware = False + + if not self.store.graph_aware: + raise Exception("Dataset must be backed by a graph-aware store!") + + + def __str__(self) -> str: + pattern = ( + "[a rdflib:Dataset;rdflib:storage " "[a rdflib:Store;rdfs:label '%s']]" + ) + return pattern % self.store.__class__.__name__ + + # type error: Return type "tuple[Type[Dataset], tuple[Store, bool]]" of "__reduce__" incompatible with return type "tuple[Type[Graph], tuple[Store, IdentifiedNode]]" in supertype "Graph" + def __reduce__(self) -> tuple[type[Dataset], tuple[Store, bool]]: # type: ignore[override] + return type(self), (self.store, self.default_union) + + def add_named_graph( + self, + graph: Graph, + name: Optional[IdentifiedNode | str] = None, + base: str | None = None, + ) -> Dataset: + if name is None: + from rdflib.term import _SKOLEM_DEFAULT_AUTHORITY, rdflib_skolem_genid + + self.bind( + "genid", + _SKOLEM_DEFAULT_AUTHORITY + rdflib_skolem_genid, + override=False, + ) + name = BNode().skolemize() + + graph.base = base + + self.store.add_graph(graph, name) + return self + + def has_named_graph(self, name: IdentifiedNode) -> bool: + raise NotImplementedError + + def remove_named_graph(self, name: IdentifiedNode) -> Dataset: + raise NotImplementedError + + def get_named_graph(self, name: IdentifiedNode) -> Graph: + raise NotImplementedError + + def replace_named_graph(self, graph: Graph, name: IdentifiedNode) -> Dataset: + raise NotImplementedError + + def parse( + self, + source: ( + IO[bytes] | TextIO | InputSource | str | bytes | pathlib.PurePath | None + ) = None, + publicID: str | None = None, # noqa: N803 + format: str | None = None, + location: str | None = None, + file: BinaryIO | TextIO | None = None, + data: str | bytes | None = None, + **args: Any, + ) -> Dataset: + raise NotImplementedError + + def graphs( + self, triple: _TripleType | None = None + ) -> Generator[tuple[IdentifiedNode, Graph], None, None]: + raise NotImplementedError + + def triples( + self, + triple: _TripleSliceType | _QuadSliceType | None = None, + graph: _GraphSliceType | None = None, + ) -> Generator[_TripleType, None, None]: + if graph is None: # by default, include the default and all named graphs + graph = [GraphType.DEFAULT, GraphType.NAMED] + raise NotImplementedError + + # type error: Return type "Generator[tuple[Node, Node, Node, Optional[Node]], None, None]" of "quads" incompatible with return type "Generator[tuple[Node, Node, Node, Optional[Graph]], None, None]" in supertype "ConjunctiveGraph" + def quads( # type: ignore[override] + self, + quad: _QuadSliceType | None = None, + graph: _GraphSliceType | None = None, + ) -> Generator[_QuadType, None, None]: + if graph is None: # by default, include the default and all named graphs + graph = [GraphType.DEFAULT, GraphType.NAMED] + raise NotImplementedError + + def default_graph(self) -> Graph: + return self.default_graph + + # type error: Return type "Generator[tuple[Node, URIRef, Node, Optional[IdentifiedNode]], None, None]" of "__iter__" incompatible with return type "Generator[tuple[IdentifiedNode, IdentifiedNode, Union[IdentifiedNode, Literal]], None, None]" in supertype "Graph" + def __iter__( # type: ignore[override] + self, + ) -> Generator[_QuadType, None, None]: + """Iterates over all quads in the store""" + return self.quads() + + def add(self, quad: _QuadType) -> Dataset: + raise NotImplementedError + + def remove(self, quad: _QuadType) -> Dataset: + raise NotImplementedError + + def subjects( + self, + predicates: _PredicateSliceType | None = None, + objects: _ObjectSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[IdentifiedNode, None, None]: + raise NotImplementedError + + def predicates( + self, + subjects: _SubjectSliceType | None = None, + objects: _ObjectSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[URIRef, None, None]: + raise NotImplementedError + + def objects( + self, + subjects: _SubjectSliceType | None = None, + predicates: _PredicateSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[Node, None, None]: + raise NotImplementedError + + def subject_objects( + self, + predicates: _PredicateSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[tuple[IdentifiedNode, Node], None, None]: + raise NotImplementedError + + def subject_predicates( + self, + objects: _ObjectSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[tuple[IdentifiedNode, URIRef], None, None]: + raise NotImplementedError + + def predicate_objects( + self, + subjects: _SubjectSliceType | None = None, + graphs: _GraphSliceType | None = None, + ) -> Generator[tuple[URIRef, Node], None, None]: + raise NotImplementedError + + def value(self, + subject: Optional[IdentifiedNode] = None, + predicate: Optional[URIRef] = None, + object: Optional[Node] = None, + graph: Optional[IdentifiedNode] = None + ): + raise NotImplementedError + + def query(self, query): + raise NotImplementedError + + def update(self, update): + raise NotImplementedError + + # the following methods are taken from Graph. It would make sense to extract them + # as shared methods between Dataset and Graph + + def qname(self, uri: str) -> str: + return self.namespace_manager.qname(uri) + + def compute_qname(self, uri: str, generate: bool = True) -> Tuple[str, URIRef, str]: + return self.namespace_manager.compute_qname(uri, generate) + + def bind( + self, + prefix: Optional[str], + namespace: Any, # noqa: F811 + override: bool = True, + replace: bool = False, + ) -> None: + """Bind prefix to namespace + + If override is True will bind namespace to given prefix even + if namespace was already bound to a different prefix. + + if replace, replace any existing prefix with the new namespace + + for example: graph.bind("foaf", "http://xmlns.com/foaf/0.1/") + + """ + # TODO FIXME: This method's behaviour should be simplified and made + # more robust. If the method cannot do what it is asked it should raise + # an exception, it is also unclear why this method has all the + # different modes. It seems to just make it more complex to use, maybe + # it should be clarified when someone will need to use override=False + # and replace=False. And also why silent failure here is preferred over + # raising an exception. + return self.namespace_manager.bind( + prefix, namespace, override=override, replace=replace + ) + + def namespaces(self) -> Generator[Tuple[str, URIRef], None, None]: + """Generator over all the prefix, namespace tuples""" + for prefix, namespace in self.namespace_manager.namespaces(): # noqa: F402 + yield prefix, namespace + + + def n3(self, namespace_manager: Optional[NamespaceManager] = None) -> str: + """Return an n3 identifier for the Dataset.""" + raise NotImplementedError + + def __reduce__(self) -> Tuple[Type[Graph], Tuple[Store, Node]]: + """Support for pickling the Dataset.""" + raise NotImplementedError + + def isomorphic(self, other: Dataset, compare_graphs: bool = True) -> bool: + """Check if two Datasets are isomorphic. + + - If `compare_graphs` is True, checks that all named graphs are isomorphic as well. + - Otherwise, only the default graphs are compared. + """ + raise NotImplementedError + + def connected(self) -> bool: + """Check if the Dataset is connected, treating it as undirected.""" + raise NotImplementedError + + def all_nodes(self) -> Set[Node]: + """Return all nodes in the Dataset.""" + raise NotImplementedError + + def collection(self, identifier: Node) -> Collection: + """Create a new Collection instance for the given identifier.""" + raise NotImplementedError + + def resource(self, identifier: Union[Node, str]) -> Resource: + """Create a new Resource instance for the given identifier.""" + raise NotImplementedError + + def _process_skolem_tuples(self, target: Graph, func: Callable[[Tuple[Node, Node, Node]], Tuple[Node, Node, Node]]) -> None: + """Helper function to apply a transformation to skolemized triples.""" + raise NotImplementedError + + def skolemize( + self, + new_dataset: Optional[Dataset] = None, + bnode: Optional[BNode] = None, + authority: Optional[str] = None, + basepath: Optional[str] = None, + ) -> Dataset: + """Convert Blank Nodes within the Dataset to skolemized URIs.""" + raise NotImplementedError + + def de_skolemize( + self, new_dataset: Optional[Dataset] = None, uriref: Optional[URIRef] = None + ) -> Dataset: + """Convert skolemized URIs back into blank nodes.""" + raise NotImplementedError + + def cbd(self, resource: Node, graphs: _GraphSliceType = None) -> Graph: + """Retrieve the Concise Bounded Description (CBD) of a resource. + + If `graphs` is specified, the CBD is computed from those graphs only. + Otherwise, it is computed from the entire Dataset. + """ + raise NotImplementedError diff --git a/rdflib/graph.py b/rdflib/graph.py index 9ba3dd396..ccfeaf483 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -4,7 +4,6 @@ * :class:`~rdflib.graph.Graph` * :class:`~rdflib.graph.QuotedGraph` -* :class:`~rdflib.graph.ConjunctiveGraph` * :class:`~rdflib.graph.Dataset` Graph @@ -16,23 +15,6 @@ see :class:`~rdflib.graph.Graph` -Conjunctive Graph ------------------ - -.. warning:: - ConjunctiveGraph is deprecated, use :class:`~rdflib.graph.Dataset` instead. - -A Conjunctive Graph is the most relevant collection of graphs that are -considered to be the boundary for closed world assumptions. This -boundary is equivalent to that of the store instance (which is itself -uniquely identified and distinct from other instances of -:class:`~rdflib.store.Store` that signify other Conjunctive Graphs). It is -equivalent to all the named graphs within it and associated with a -``_default_`` graph which is automatically assigned a -:class:`~rdflib.term.BNode` for an identifier - if one isn't given. - -see :class:`~rdflib.graph.ConjunctiveGraph` - Quoted graph ------------ @@ -360,15 +342,37 @@ Optional[_ObjectType], Optional["_ContextType"], ] +_TripleOrQuadSelectorType = Union["_TripleSelectorType", "_QuadSelectorType"] +_TriplePathType = tuple["_SubjectType", Path, "_ObjectType"] +_TripleOrTriplePathType = Union["_TripleType", "_TriplePathType"] _TripleOrQuadSelectorType: te.TypeAlias = Union[_TripleSelectorType, _QuadSelectorType] +_SubjectSliceType: te.TypeAlias = tuple[ + list[_SubjectType] | tuple[_SubjectType, ...] | _SubjectType | None, + ] +_PredicateSliceType: te.TypeAlias = tuple[ + list[_PredicateType] | tuple[_PredicateType, ...] | _PredicateType | None, + ] +_ObjectSliceType: te.TypeAlias = tuple[ + list[_ObjectType] | tuple[_ObjectType, ...] | _ObjectType | None, + ] +_GraphSliceType: te.TypeAlias = tuple[ + list[_ContextIdentifierType] | tuple[_ContextIdentifierType, ...] | _ContextIdentifierType | None, + ] -_GraphT = TypeVar("_GraphT", bound="Graph") -_ConjunctiveGraphT = TypeVar("_ConjunctiveGraphT", bound="ConjunctiveGraph") -_DatasetT = TypeVar("_DatasetT", bound="Dataset") -_QuotedGraphT = TypeVar("_QuotedGraphT", bound="QuotedGraph") +_TripleSliceType: te.TypeAlias = tuple[ + _SubjectSliceType, + _PredicateSliceType, + _ObjectSliceType, + ] +_QuadSliceType: te.TypeAlias = tuple[ + _SubjectSliceType, + _PredicateSliceType, + _ObjectSliceType, + _GraphSliceType, +] -_builtin_set_t = set +_GraphT = TypeVar("_GraphT", bound="Graph") # type error: Function "Type[Literal]" could always be true in boolean contex assert Literal # type: ignore[truthy-function] # avoid warning @@ -383,17 +387,12 @@ __all__ = [ "Graph", - "ConjunctiveGraph", "QuotedGraph", "Seq", "ModificationException", - "Dataset", "UnSupportedAggregateOperation", - "ReadOnlyGraphAggregate", "BatchAddGraph", - "_ConjunctiveGraphT", "_ContextIdentifierType", - "_DatasetT", "_GraphT", "_ObjectType", "_OptionalIdentifiedQuadType", @@ -493,17 +492,12 @@ class Graph(Node): def __init__( self, store: Store | str = "default", - identifier: _ContextIdentifierType | str | None = None, namespace_manager: NamespaceManager | None = None, base: str | None = None, bind_namespaces: _NamespaceSetString = "rdflib", ): super(Graph, self).__init__() self.base = base - self.__identifier: _ContextIdentifierType - self.__identifier = identifier or BNode() # type: ignore[assignment] - if not isinstance(self.__identifier, IdentifiedNode): - self.__identifier = URIRef(self.__identifier) # type: ignore[unreachable] self.__store: Store if not isinstance(store, Store): # TODO: error handling @@ -529,10 +523,6 @@ def __getnewargs__(self) -> tuple[Any, ...]: def store(self) -> Store: return self.__store - @property - def identifier(self) -> _ContextIdentifierType: - return self.__identifier - @property def namespace_manager(self) -> NamespaceManager: """ @@ -1665,19 +1655,13 @@ def query( initBindings = initBindings or {} # noqa: N806 initNs = initNs or dict(self.namespaces()) # noqa: N806 - if self.default_union: - query_graph = "__UNION__" - elif isinstance(self, ConjunctiveGraph): - query_graph = self.default_context.identifier - else: - query_graph = self.identifier if hasattr(self.store, "query") and use_store_provided: try: return self.store.query( query_object, initNs, initBindings, - query_graph, + "__UNION__", **kwargs, ) except NotImplementedError: @@ -2007,730 +1991,7 @@ def add_to_cbd(uri: _SubjectType) -> None: _ContextType: te.TypeAlias = Union[Graph] -class ConjunctiveGraph(Graph): - """A ConjunctiveGraph is an (unnamed) aggregation of all the named - graphs in a store. - - .. warning:: - ConjunctiveGraph is deprecated, use :class:`~rdflib.graph.Dataset` instead. - - It has a ``default`` graph, whose name is associated with the - graph throughout its life. :meth:`__init__` can take an identifier - to use as the name of this default graph or it will assign a - BNode. - - All methods that add triples work against this default graph. - - All queries are carried out against the union of all graphs. - """ - - default_context: _ContextType - - def __init__( - self, - store: Store | str = "default", - identifier: IdentifiedNode | str | None = None, - default_graph_base: str | None = None, - ): - super(ConjunctiveGraph, self).__init__(store, identifier=identifier) - - if type(self) is ConjunctiveGraph: - warnings.warn( - "ConjunctiveGraph is deprecated, use Dataset instead.", - DeprecationWarning, - stacklevel=2, - ) - - assert self.store.context_aware, ( - "ConjunctiveGraph must be backed by" " a context aware store." - ) - self.context_aware = True - self.default_union = True # Conjunctive! - self.default_context: _ContextType = Graph( - store=self.store, identifier=identifier or BNode(), base=default_graph_base - ) - - def __getnewargs__(self) -> tuple[Any, ...]: - return (self.store, self.__identifier, self.default_context.base) - - def __str__(self) -> str: - pattern = ( - "[a rdflib:ConjunctiveGraph;rdflib:storage " - "[a rdflib:Store;rdfs:label '%s']]" - ) - return pattern % self.store.__class__.__name__ - - @overload - def _spoc( - self, - triple_or_quad: _QuadType, - default: bool = False, - ) -> _QuadType: ... - - @overload - def _spoc( - self, - triple_or_quad: _TripleType | _OptionalQuadType, - default: bool = False, - ) -> _OptionalQuadType: ... - - @overload - def _spoc( - self, - triple_or_quad: None, - default: bool = False, - ) -> tuple[None, None, None, Graph | None]: ... - - @overload - def _spoc( - self, - triple_or_quad: _TripleOrQuadPatternType | None, - default: bool = False, - ) -> _QuadPatternType: ... - - @overload - def _spoc( - self, - triple_or_quad: _TripleOrQuadSelectorType, - default: bool = False, - ) -> _QuadSelectorType: ... - - @overload - def _spoc( - self, - triple_or_quad: _TripleOrQuadSelectorType | None, - default: bool = False, - ) -> _QuadSelectorType: ... - - def _spoc( - self, - triple_or_quad: _TripleOrQuadSelectorType | None, - default: bool = False, - ) -> _QuadSelectorType: - """ - helper method for having methods that support - either triples or quads - """ - if triple_or_quad is None: - return (None, None, None, self.default_context if default else None) - if len(triple_or_quad) == 3: - c = self.default_context if default else None - # type error: Too many values to unpack (3 expected, 4 provided) - (s, p, o) = triple_or_quad # type: ignore[misc, unused-ignore] - elif len(triple_or_quad) == 4: - # type error: Need more than 3 values to unpack (4 expected) - (s, p, o, c) = triple_or_quad # type: ignore[misc, unused-ignore] - c = self._graph(c) - return s, p, o, c - - def __contains__(self, triple_or_quad: _TripleOrQuadSelectorType) -> bool: - """Support for 'triple/quad in graph' syntax""" - s, p, o, c = self._spoc(triple_or_quad) - for t in self.triples((s, p, o), context=c): - return True - return False - - def add( - self: _ConjunctiveGraphT, - triple_or_quad: _TripleOrOptionalQuadType, - ) -> _ConjunctiveGraphT: - """ - Add a triple or quad to the store. - - if a triple is given it is added to the default context - """ - - s, p, o, c = self._spoc(triple_or_quad, default=True) - - _assertnode(s, p, o) - - # type error: Argument "context" to "add" of "Store" has incompatible type "Optional[Graph]"; expected "Graph" - self.store.add((s, p, o), context=c, quoted=False) # type: ignore[arg-type] - return self - - @overload - def _graph(self, c: Graph | _ContextIdentifierType | str) -> Graph: ... - - @overload - def _graph(self, c: None) -> None: ... - - def _graph(self, c: Graph | _ContextIdentifierType | str | None) -> Graph | None: - if c is None: - return None - if not isinstance(c, Graph): - return self.get_context(c) - else: - return c - - def addN( # noqa: N802 - self: _ConjunctiveGraphT, quads: Iterable[_QuadType] - ) -> _ConjunctiveGraphT: - """Add a sequence of triples with context""" - - self.store.addN( - (s, p, o, self._graph(c)) for s, p, o, c in quads if _assertnode(s, p, o) - ) - return self - - # type error: Argument 1 of "remove" is incompatible with supertype "Graph"; supertype defines the argument type as "tuple[Optional[Node], Optional[Node], Optional[Node]]" - def remove(self: _ConjunctiveGraphT, triple_or_quad: _TripleOrOptionalQuadType) -> _ConjunctiveGraphT: # type: ignore[override] - """ - Removes a triple or quads - - if a triple is given it is removed from all contexts - - a quad is removed from the given context only - - """ - s, p, o, c = self._spoc(triple_or_quad) - - self.store.remove((s, p, o), context=c) - return self - - @overload - def triples( - self, - triple_or_quad: _TripleOrQuadPatternType, - context: _ContextType | None = ..., - ) -> Generator[_TripleType, None, None]: ... - - @overload - def triples( - self, - triple_or_quad: _TripleOrQuadPathPatternType, - context: _ContextType | None = ..., - ) -> Generator[_TriplePathType, None, None]: ... - - @overload - def triples( - self, - triple_or_quad: _TripleOrQuadSelectorType, - context: _ContextType | None = ..., - ) -> Generator[_TripleOrTriplePathType, None, None]: ... - - def triples( - self, - triple_or_quad: _TripleOrQuadSelectorType, - context: _ContextType | None = None, - ) -> Generator[_TripleOrTriplePathType, None, None]: - """ - Iterate over all the triples in the entire conjunctive graph - - For legacy reasons, this can take the context to query either - as a fourth element of the quad, or as the explicit context - keyword parameter. The kw param takes precedence. - """ - - s, p, o, c = self._spoc(triple_or_quad) - context = self._graph(context or c) - - if self.default_union: - if context == self.default_context: - context = None - else: - if context is None: - context = self.default_context - - if isinstance(p, Path): - if context is None: - context = self - - for s, o in p.eval(context, s, o): - yield s, p, o - else: - for (s, p, o), cg in self.store.triples((s, p, o), context=context): - yield s, p, o - - def quads( - self, triple_or_quad: _TripleOrQuadPatternType | None = None - ) -> Generator[_OptionalQuadType, None, None]: - """Iterate over all the quads in the entire conjunctive graph""" - - s, p, o, c = self._spoc(triple_or_quad) - - for (s, p, o), cg in self.store.triples((s, p, o), context=c): - for ctx in cg: - yield s, p, o, ctx - - def triples_choices( - self, - triple: ( - tuple[ - list[_SubjectType] | tuple[_SubjectType, ...], - _PredicateType, - _ObjectType | None, - ] - | tuple[ - _SubjectType | None, - list[_PredicateType] | tuple[_PredicateType, ...], - _ObjectType | None, - ] - | tuple[ - _SubjectType | None, - _PredicateType, - list[_ObjectType] | tuple[_ObjectType, ...], - ] - ), - context: _ContextType | None = None, - ) -> Generator[_TripleType, None, None]: - """Iterate over all the triples in the entire conjunctive graph""" - s, p, o = triple - if context is None: - if not self.default_union: - context = self.default_context - else: - context = self._graph(context) - # type error: Argument 1 to "triples_choices" of "Store" has incompatible type "tuple[Union[list[Node], Node], Union[Node, list[Node]], Union[Node, list[Node]]]"; expected "Union[tuple[list[Node], Node, Node], tuple[Node, list[Node], Node], tuple[Node, Node, list[Node]]]" - # type error note: unpacking discards type info - for (s1, p1, o1), cg in self.store.triples_choices((s, p, o), context=context): # type: ignore[arg-type] - yield s1, p1, o1 - - def __len__(self) -> int: - """Number of triples in the entire conjunctive graph""" - return self.store.__len__() - - def contexts( - self, triple: _TripleType | None = None - ) -> Generator[_ContextType, None, None]: - """Iterate over all contexts in the graph - - If triple is specified, iterate over all contexts the triple is in. - """ - for context in self.store.contexts(triple): - if isinstance(context, Graph): - # TODO: One of these should never happen and probably - # should raise an exception rather than smoothing over - # the weirdness - see #225 - yield context - else: - # type error: Statement is unreachable - yield self.get_context(context) # type: ignore[unreachable] - - def get_graph(self, identifier: _ContextIdentifierType) -> Graph | None: - """Returns the graph identified by given identifier""" - return [x for x in self.contexts() if x.identifier == identifier][0] - - def get_context( - self, - identifier: _ContextIdentifierType | str | None, - quoted: bool = False, - base: str | None = None, - ) -> Graph: - """Return a context graph for the given identifier - - identifier must be a URIRef or BNode. - """ - return Graph( - store=self.store, - identifier=identifier, - namespace_manager=self.namespace_manager, - base=base, - ) - - def remove_context(self, context: _ContextType) -> None: - """Removes the given context from the graph""" - self.store.remove((None, None, None), context) - - def context_id(self, uri: str, context_id: str | None = None) -> URIRef: - """URI#context""" - uri = uri.split("#", 1)[0] - if context_id is None: - context_id = "#context" - return URIRef(context_id, base=uri) - - def parse( - self, - source: ( - IO[bytes] | TextIO | InputSource | str | bytes | pathlib.PurePath | None - ) = None, - publicID: str | None = None, # noqa: N803 - format: str | None = None, - location: str | None = None, - file: BinaryIO | TextIO | None = None, - data: str | bytes | None = None, - **args: Any, - ) -> Graph: - """ - Parse source adding the resulting triples to its own context (sub graph - of this graph). - - See :meth:`rdflib.graph.Graph.parse` for documentation on arguments. - - If the source is in a format that does not support named graphs its triples - will be added to the default graph - (i.e. :attr:`ConjunctiveGraph.default_context`). - - :Returns: - - The graph into which the source was parsed. In the case of n3 it returns - the root context. - - .. caution:: - - This method can access directly or indirectly requested network or - file resources, for example, when parsing JSON-LD documents with - ``@context`` directives that point to a network location. - - When processing untrusted or potentially malicious documents, - measures should be taken to restrict network and file access. - - For information on available security measures, see the RDFLib - :doc:`Security Considerations ` - documentation. - - *Changed in 7.0*: The ``publicID`` argument is no longer used as the - identifier (i.e. name) of the default graph as was the case before - version 7.0. In the case of sources that do not support named graphs, - the ``publicID`` parameter will also not be used as the name for the - graph that the data is loaded into, and instead the triples from sources - that do not support named graphs will be loaded into the default graph - (i.e. :attr:`ConjunctiveGraph.default_context`). - """ - - source = create_input_source( - source=source, - publicID=publicID, - location=location, - file=file, - data=data, - format=format, - ) - - # NOTE on type hint: `xml.sax.xmlreader.InputSource.getPublicId` has no - # type annotations but given that systemId should be a string, and - # given that there is no specific mention of type for publicId, it - # seems reasonable to assume it should also be a string. Furthermore, - # create_input_source will ensure that publicId is not None, though it - # would be good if this guarantee was made more explicit i.e. by type - # hint on InputSource (TODO/FIXME). - - context = self.default_context - context.parse(source, publicID=publicID, format=format, **args) - # TODO: FIXME: This should not return context, but self. - return context - def __reduce__(self) -> tuple[type[Graph], tuple[Store, _ContextIdentifierType]]: - return ConjunctiveGraph, (self.store, self.identifier) - - -DATASET_DEFAULT_GRAPH_ID = URIRef("urn:x-rdflib:default") - - -class Dataset(ConjunctiveGraph): - """ - An RDFLib Dataset is an object that stores multiple Named Graphs - instances of - RDFLib Graph identified by IRI - within it and allows whole-of-dataset or single - Graph use. - - RDFLib's Dataset class is based on the `RDF 1.2. 'Dataset' definition - `_: - - .. - - An RDF dataset is a collection of RDF graphs, and comprises: - - - Exactly one default graph, being an RDF graph. The default graph does not - have a name and MAY be empty. - - Zero or more named graphs. Each named graph is a pair consisting of an IRI or - a blank node (the graph name), and an RDF graph. Graph names are unique - within an RDF dataset. - - Accordingly, a Dataset allows for `Graph` objects to be added to it with - :class:`rdflib.term.URIRef` or :class:`rdflib.term.BNode` identifiers and always - creats a default graph with the :class:`rdflib.term.URIRef` identifier - :code:`urn:x-rdflib:default`. - - Dataset extends Graph's Subject, Predicate, Object (s, p, o) 'triple' - structure to include a graph identifier - archaically called Context - producing - 'quads' of s, p, o, g. - - Triples, or quads, can be added to a Dataset. Triples, or quads with the graph - identifer :code:`urn:x-rdflib:default` go into the default graph. - - .. note:: Dataset builds on the `ConjunctiveGraph` class but that class's direct - use is now deprecated (since RDFLib 7.x) and it should not be used. - `ConjunctiveGraph` will be removed from future RDFLib versions. - - Examples of usage and see also the examples/datast.py file: - - >>> # Create a new Dataset - >>> ds = Dataset() - >>> # simple triples goes to default graph - >>> ds.add(( - ... URIRef("http://example.org/a"), - ... URIRef("http://www.example.org/b"), - ... Literal("foo") - ... )) # doctest: +ELLIPSIS - )> - >>> - >>> # Create a graph in the dataset, if the graph name has already been - >>> # used, the corresponding graph will be returned - >>> # (ie, the Dataset keeps track of the constituent graphs) - >>> g = ds.graph(URIRef("http://www.example.com/gr")) - >>> - >>> # add triples to the new graph as usual - >>> g.add(( - ... URIRef("http://example.org/x"), - ... URIRef("http://example.org/y"), - ... Literal("bar") - ... )) # doctest: +ELLIPSIS - )> - >>> # alternatively: add a quad to the dataset -> goes to the graph - >>> ds.add(( - ... URIRef("http://example.org/x"), - ... URIRef("http://example.org/z"), - ... Literal("foo-bar"), - ... g - ... )) # doctest: +ELLIPSIS - )> - >>> - >>> # querying triples return them all regardless of the graph - >>> for t in ds.triples((None,None,None)): # doctest: +SKIP - ... print(t) # doctest: +NORMALIZE_WHITESPACE - (rdflib.term.URIRef("http://example.org/a"), - rdflib.term.URIRef("http://www.example.org/b"), - rdflib.term.Literal("foo")) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/z"), - rdflib.term.Literal("foo-bar")) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/y"), - rdflib.term.Literal("bar")) - >>> - >>> # querying quads() return quads; the fourth argument can be unrestricted - >>> # (None) or restricted to a graph - >>> for q in ds.quads((None, None, None, None)): # doctest: +SKIP - ... print(q) # doctest: +NORMALIZE_WHITESPACE - (rdflib.term.URIRef("http://example.org/a"), - rdflib.term.URIRef("http://www.example.org/b"), - rdflib.term.Literal("foo"), - None) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/y"), - rdflib.term.Literal("bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/z"), - rdflib.term.Literal("foo-bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - >>> - >>> # unrestricted looping is equivalent to iterating over the entire Dataset - >>> for q in ds: # doctest: +SKIP - ... print(q) # doctest: +NORMALIZE_WHITESPACE - (rdflib.term.URIRef("http://example.org/a"), - rdflib.term.URIRef("http://www.example.org/b"), - rdflib.term.Literal("foo"), - None) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/y"), - rdflib.term.Literal("bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/z"), - rdflib.term.Literal("foo-bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - >>> - >>> # resticting iteration to a graph: - >>> for q in ds.quads((None, None, None, g)): # doctest: +SKIP - ... print(q) # doctest: +NORMALIZE_WHITESPACE - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/y"), - rdflib.term.Literal("bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - (rdflib.term.URIRef("http://example.org/x"), - rdflib.term.URIRef("http://example.org/z"), - rdflib.term.Literal("foo-bar"), - rdflib.term.URIRef("http://www.example.com/gr")) - >>> # Note that in the call above - - >>> # ds.quads((None,None,None,"http://www.example.com/gr")) - >>> # would have been accepted, too - >>> - >>> # graph names in the dataset can be queried: - >>> for c in ds.graphs(): # doctest: +SKIP - ... print(c.identifier) # doctest: - urn:x-rdflib:default - http://www.example.com/gr - >>> # A graph can be created without specifying a name; a skolemized genid - >>> # is created on the fly - >>> h = ds.graph() - >>> for c in ds.graphs(): # doctest: +SKIP - ... print(c) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS - DEFAULT - https://rdflib.github.io/.well-known/genid/rdflib/N... - http://www.example.com/gr - >>> # Note that the Dataset.graphs() call returns names of empty graphs, - >>> # too. This can be restricted: - >>> for c in ds.graphs(empty=False): # doctest: +SKIP - ... print(c) # doctest: +NORMALIZE_WHITESPACE - DEFAULT - http://www.example.com/gr - >>> - >>> # a graph can also be removed from a dataset via ds.remove_graph(g) - - ... versionadded:: 4.0 - """ - - def __init__( - self, - store: Store | str = "default", - default_union: bool = False, - default_graph_base: str | None = None, - ): - super(Dataset, self).__init__(store=store, identifier=None) - - if not self.store.graph_aware: - raise Exception("Dataset must be backed by a graph-aware store!") - self.default_context = Graph( - store=self.store, - identifier=DATASET_DEFAULT_GRAPH_ID, - base=default_graph_base, - ) - - self.default_union = default_union - - def __getnewargs__(self) -> tuple[Any, ...]: - return (self.store, self.default_union, self.default_context.base) - - def __str__(self) -> str: - pattern = ( - "[a rdflib:Dataset;rdflib:storage " "[a rdflib:Store;rdfs:label '%s']]" - ) - return pattern % self.store.__class__.__name__ - - # type error: Return type "tuple[Type[Dataset], tuple[Store, bool]]" of "__reduce__" incompatible with return type "tuple[Type[Graph], tuple[Store, IdentifiedNode]]" in supertype "ConjunctiveGraph" - # type error: Return type "tuple[Type[Dataset], tuple[Store, bool]]" of "__reduce__" incompatible with return type "tuple[Type[Graph], tuple[Store, IdentifiedNode]]" in supertype "Graph" - def __reduce__(self) -> tuple[type[Dataset], tuple[Store, bool]]: # type: ignore[override] - return type(self), (self.store, self.default_union) - - def __getstate__(self) -> tuple[Store, _ContextIdentifierType, _ContextType, bool]: - return self.store, self.identifier, self.default_context, self.default_union - - def __setstate__( - self, state: tuple[Store, _ContextIdentifierType, _ContextType, bool] - ) -> None: - # type error: Property "store" defined in "Graph" is read-only - # type error: Property "identifier" defined in "Graph" is read-only - self.store, self.identifier, self.default_context, self.default_union = state # type: ignore[misc] - - def graph( - self, - identifier: _ContextIdentifierType | _ContextType | str | None = None, - base: str | None = None, - ) -> Graph: - if identifier is None: - from rdflib.term import _SKOLEM_DEFAULT_AUTHORITY, rdflib_skolem_genid - - self.bind( - "genid", - _SKOLEM_DEFAULT_AUTHORITY + rdflib_skolem_genid, - override=False, - ) - identifier = BNode().skolemize() - - g = self._graph(identifier) - g.base = base - - self.store.add_graph(g) - return g - - def parse( - self, - source: ( - IO[bytes] | TextIO | InputSource | str | bytes | pathlib.PurePath | None - ) = None, - publicID: str | None = None, # noqa: N803 - format: str | None = None, - location: str | None = None, - file: BinaryIO | TextIO | None = None, - data: str | bytes | None = None, - **args: Any, - ) -> Graph: - """ - Parse an RDF source adding the resulting triples to the Graph. - - See :meth:`rdflib.graph.Graph.parse` for documentation on arguments. - - The source is specified using one of source, location, file or data. - - If the source is in a format that does not support named graphs its triples - will be added to the default graph - (i.e. :attr:`.Dataset.default_context`). - - .. caution:: - - This method can access directly or indirectly requested network or - file resources, for example, when parsing JSON-LD documents with - ``@context`` directives that point to a network location. - - When processing untrusted or potentially malicious documents, - measures should be taken to restrict network and file access. - - For information on available security measures, see the RDFLib - :doc:`Security Considerations ` - documentation. - - *Changed in 7.0*: The ``publicID`` argument is no longer used as the - identifier (i.e. name) of the default graph as was the case before - version 7.0. In the case of sources that do not support named graphs, - the ``publicID`` parameter will also not be used as the name for the - graph that the data is loaded into, and instead the triples from sources - that do not support named graphs will be loaded into the default graph - (i.e. :attr:`.Dataset.default_context`). - """ - - c = ConjunctiveGraph.parse( - self, source, publicID, format, location, file, data, **args - ) - self.graph(c) - return c - - def add_graph(self, g: _ContextIdentifierType | _ContextType | str | None) -> Graph: - """alias of graph for consistency""" - return self.graph(g) - - def remove_graph( - self: _DatasetT, g: _ContextIdentifierType | _ContextType | str | None - ) -> _DatasetT: - if not isinstance(g, Graph): - g = self.get_context(g) - - self.store.remove_graph(g) - if g is None or g == self.default_context: - # default graph cannot be removed - # only triples deleted, so add it back in - self.store.add_graph(self.default_context) - return self - - def contexts( - self, triple: _TripleType | None = None - ) -> Generator[_ContextType, None, None]: - default = False - for c in super(Dataset, self).contexts(triple): - default |= c.identifier == DATASET_DEFAULT_GRAPH_ID - yield c - if not default: - yield self.graph(DATASET_DEFAULT_GRAPH_ID) - - graphs = contexts - - # type error: Return type "Generator[tuple[Node, Node, Node, Optional[Node]], None, None]" of "quads" incompatible with return type "Generator[tuple[Node, Node, Node, Optional[Graph]], None, None]" in supertype "ConjunctiveGraph" - def quads( # type: ignore[override] - self, quad: _TripleOrQuadPatternType | None = None - ) -> Generator[_OptionalIdentifiedQuadType, None, None]: - for s, p, o, c in super(Dataset, self).quads(quad): - # type error: Item "None" of "Optional[Graph]" has no attribute "identifier" - if c.identifier == self.default_context: # type: ignore[union-attr] - yield s, p, o, None - else: - # type error: Item "None" of "Optional[Graph]" has no attribute "identifier" [union-attr] - yield s, p, o, c.identifier # type: ignore[union-attr] - - # type error: Return type "Generator[tuple[Node, URIRef, Node, Optional[IdentifiedNode]], None, None]" of "__iter__" incompatible with return type "Generator[tuple[IdentifiedNode, IdentifiedNode, Union[IdentifiedNode, Literal]], None, None]" in supertype "Graph" - def __iter__( # type: ignore[override] - self, - ) -> Generator[_OptionalIdentifiedQuadType, None, None]: - """Iterates over all quads in the store""" - return self.quads((None, None, None, None)) class QuotedGraph(Graph, IdentifiedNode): @@ -2890,229 +2151,6 @@ def __str__(self) -> str: return "This operation is not supported by ReadOnlyGraphAggregate " "instances" -class ReadOnlyGraphAggregate(ConjunctiveGraph): - """Utility class for treating a set of graphs as a single graph - - Only read operations are supported (hence the name). Essentially a - ConjunctiveGraph over an explicit subset of the entire store. - """ - - def __init__(self, graphs: list[Graph], store: Union[str, Store] = "default"): - if store is not None: - super(ReadOnlyGraphAggregate, self).__init__(store) - Graph.__init__(self, store) - self.__namespace_manager = None - - assert ( - isinstance(graphs, list) - and graphs - and [g for g in graphs if isinstance(g, Graph)] - ), "graphs argument must be a list of Graphs!!" - self.graphs = graphs - - def __repr__(self) -> str: - return "" % len(self.graphs) - - def destroy(self, configuration: str) -> NoReturn: - raise ModificationException() - - # Transactional interfaces (optional) - def commit(self) -> NoReturn: - raise ModificationException() - - def rollback(self) -> NoReturn: - raise ModificationException() - - def open(self, configuration: str, create: bool = False) -> None: - # TODO: is there a use case for this method? - for graph in self.graphs: - # type error: Too many arguments for "open" of "Graph" - # type error: Argument 1 to "open" of "Graph" has incompatible type "ReadOnlyGraphAggregate"; expected "str" [arg-type] - # type error: Argument 2 to "open" of "Graph" has incompatible type "str"; expected "bool" [arg-type] - graph.open(self, configuration, create) # type: ignore[call-arg, arg-type] - - # type error: Signature of "close" incompatible with supertype "Graph" - def close(self) -> None: # type: ignore[override] - for graph in self.graphs: - graph.close() - - def add(self, triple: _TripleOrOptionalQuadType) -> NoReturn: - raise ModificationException() - - def addN(self, quads: Iterable[_QuadType]) -> NoReturn: # noqa: N802 - raise ModificationException() - - # type error: Argument 1 of "remove" is incompatible with supertype "Graph"; supertype defines the argument type as "tuple[Optional[Node], Optional[Node], Optional[Node]]" - def remove(self, triple: _TripleOrOptionalQuadType) -> NoReturn: # type: ignore[override] - raise ModificationException() - - # type error: Signature of "triples" incompatible with supertype "ConjunctiveGraph" - @overload # type: ignore[override] - def triples( - self, - triple: _TriplePatternType, - ) -> Generator[_TripleType, None, None]: ... - - @overload - def triples( - self, - triple: _TriplePathPatternType, - ) -> Generator[_TriplePathType, None, None]: ... - - @overload - def triples( - self, - triple: _TripleSelectorType, - ) -> Generator[_TripleOrTriplePathType, None, None]: ... - - def triples( - self, - triple: _TripleSelectorType, - ) -> Generator[_TripleOrTriplePathType, None, None]: - s, p, o = triple - for graph in self.graphs: - if isinstance(p, Path): - for s, o in p.eval(self, s, o): - yield s, p, o - else: - for s1, p1, o1 in graph.triples((s, p, o)): - yield s1, p1, o1 - - def __contains__(self, triple_or_quad: _TripleOrQuadSelectorType) -> bool: - context = None - if len(triple_or_quad) == 4: - # type error: Tuple index out of range - context = triple_or_quad[3] # type: ignore [misc, unused-ignore] - for graph in self.graphs: - if context is None or graph.identifier == context.identifier: - if triple_or_quad[:3] in graph: - return True - return False - - # type error: Signature of "quads" incompatible with supertype "ConjunctiveGraph" - def quads( # type: ignore[override] - self, triple_or_quad: _TripleOrQuadSelectorType - ) -> Generator[ - tuple[_SubjectType, Path | _PredicateType, _ObjectType, _ContextType], - None, - None, - ]: - """Iterate over all the quads in the entire aggregate graph""" - c = None - if len(triple_or_quad) == 4: - s, p, o, c = triple_or_quad - else: - s, p, o = triple_or_quad[:3] - - if c is not None: - for graph in [g for g in self.graphs if g == c]: - for s1, p1, o1 in graph.triples((s, p, o)): - yield s1, p1, o1, graph - else: - for graph in self.graphs: - for s1, p1, o1 in graph.triples((s, p, o)): - yield s1, p1, o1, graph - - def __len__(self) -> int: - return sum(len(g) for g in self.graphs) - - def __hash__(self) -> NoReturn: - raise UnSupportedAggregateOperation() - - def __cmp__(self, other) -> int: - if other is None: - return -1 - elif isinstance(other, Graph): - return -1 - elif isinstance(other, ReadOnlyGraphAggregate): - return (self.graphs > other.graphs) - (self.graphs < other.graphs) - else: - return -1 - - def __iadd__(self: _GraphT, other: Iterable[_TripleType]) -> NoReturn: - raise ModificationException() - - def __isub__(self: _GraphT, other: Iterable[_TripleType]) -> NoReturn: - raise ModificationException() - - # Conv. methods - - def triples_choices( - self, - triple: ( - tuple[ - list[_SubjectType] | tuple[_SubjectType, ...], - _PredicateType, - _ObjectType | None, - ] - | tuple[ - _SubjectType | None, - list[_PredicateType] | tuple[_PredicateType, ...], - _ObjectType | None, - ] - | tuple[ - _SubjectType | None, - _PredicateType, - list[_ObjectType] | tuple[_ObjectType, ...], - ] - ), - context: _ContextType | None = None, - ) -> Generator[_TripleType, None, None]: - subject, predicate, object_ = triple - for graph in self.graphs: - # type error: Argument 1 to "triples_choices" of "Graph" has incompatible type "tuple[Union[list[Node], Node], Union[Node, list[Node]], Union[Node, list[Node]]]"; expected "Union[tuple[list[Node], Node, Node], tuple[Node, list[Node], Node], tuple[Node, Node, list[Node]]]" - # type error note: unpacking discards type info - choices = graph.triples_choices((subject, predicate, object_)) # type: ignore[arg-type] - for s, p, o in choices: - yield s, p, o - - def qname(self, uri: str) -> str: - if hasattr(self, "namespace_manager") and self.namespace_manager: - return self.namespace_manager.qname(uri) - raise UnSupportedAggregateOperation() - - def compute_qname(self, uri: str, generate: bool = True) -> tuple[str, URIRef, str]: - if hasattr(self, "namespace_manager") and self.namespace_manager: - return self.namespace_manager.compute_qname(uri, generate) - raise UnSupportedAggregateOperation() - - # type error: Signature of "bind" incompatible with supertype "Graph" - def bind( # type: ignore[override] - self, prefix: str | None, namespace: Any, override: bool = True # noqa: F811 - ) -> NoReturn: - raise UnSupportedAggregateOperation() - - def namespaces(self) -> Generator[tuple[str, URIRef], None, None]: - if hasattr(self, "namespace_manager"): - for prefix, namespace in self.namespace_manager.namespaces(): - yield prefix, namespace - else: - for graph in self.graphs: - for prefix, namespace in graph.namespaces(): - yield prefix, namespace - - def absolutize(self, uri: str, defrag: int = 1) -> NoReturn: - raise UnSupportedAggregateOperation() - - # type error: Signature of "parse" incompatible with supertype "ConjunctiveGraph" - def parse( # type: ignore[override] - self, - source: ( - IO[bytes] | TextIO | InputSource | str | bytes | pathlib.PurePath | None - ), - publicID: str | None = None, # noqa: N803 - format: str | None = None, - **args: Any, - ) -> NoReturn: - raise ModificationException() - - def n3(self, namespace_manager: NamespaceManager | None = None) -> NoReturn: - raise UnSupportedAggregateOperation() - - def __reduce__(self) -> NoReturn: - raise UnSupportedAggregateOperation() - - @overload def _assertnode(*terms: Node) -> te.Literal[True]: ... diff --git a/test/utils/__init__.py b/test/utils/__init__.py index db57c92c2..aa9d04b9e 100644 --- a/test/utils/__init__.py +++ b/test/utils/__init__.py @@ -17,8 +17,8 @@ import rdflib.compare import rdflib.plugin -from rdflib import BNode, ConjunctiveGraph, Graph -from rdflib.graph import Dataset +from rdflib import BNode, Graph +from rdflib.dataset import Dataset from rdflib.plugin import Plugin from rdflib.term import IdentifiedNode, Identifier, Literal, Node, URIRef