Repository: RDFLib/rdflib

Issue:
Add __iter__ method to Dataset
#### Motivation

It is convenient to be able to iterate over all triples in a `Graph` instance without invoking `graph.triples((None, None, None))`. When working with a small instance of `Dataset`, there are occasions where one will want to observe all of the quads in a similar manner.

#### Notes

This is trivial to implement by simply having an introduced `__iter__` method invoke `self.quads((None, None, None, None))` in exactly [the same way that it is done in `Graph`](https://github.com/RDFLib/rdflib/blob/538446e08f992b7fc333151367dd216611dcd8e3/rdflib/graph.py#L500).

Comments:

Patch:
diff --git a/docs/intro_to_sparql.rst b/docs/intro_to_sparql.rst
index ef7aab78..72a87c66 100644
--- a/docs/intro_to_sparql.rst
+++ b/docs/intro_to_sparql.rst
@@ -39,7 +39,8 @@ For example...
         ?a foaf:name ?aname .
         ?b foaf:name ?bname .
     }"""
-
+    
+    qres = g.query(knows_query)
     for row in qres:
         print(f"{row.aname} knows {row.bname}")
 
diff --git a/docs/sphinx-requirements.txt b/docs/sphinx-requirements.txt
index 7096a117..f5e39519 100644
--- a/docs/sphinx-requirements.txt
+++ b/docs/sphinx-requirements.txt
@@ -1,3 +1,3 @@
-sphinx==4.1.1
+sphinx==4.1.2
 sphinxcontrib-apidoc
 git+https://github.com/gniezen/n3pygments.git
diff --git a/rdflib/graph.py b/rdflib/graph.py
index a689b35a..b25765ac 100644
--- a/rdflib/graph.py
+++ b/rdflib/graph.py
@@ -1,4 +1,4 @@
-from typing import Optional, Union, Type, cast, overload
+from typing import Optional, Union, Type, cast, overload, Generator, Tuple
 import logging
 from warnings import warn
 import random
@@ -29,6 +29,9 @@ assert Namespace  # avoid warning
 
 logger = logging.getLogger(__name__)
 
+# Type aliases to make unpacking what's going on a little more human friendly
+ContextNode = Union[BNode, URIRef]
+DatasetQuad = Tuple[Node, URIRef, Node, Optional[ContextNode]]
 
 __doc__ = """\
 
@@ -1759,8 +1762,8 @@ class Dataset(ConjunctiveGraph):
      rdflib.term.URIRef("http://example.org/y"),
      rdflib.term.Literal("bar"))
     >>>
-    >>> # querying quads return quads; the fourth argument can be unrestricted
-    >>> # or restricted to a graph
+    >>> # 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"),
@@ -1776,7 +1779,24 @@ class Dataset(ConjunctiveGraph):
      rdflib.term.Literal("foo-bar"),
      rdflib.term.URIRef("http://www.example.com/gr"))
     >>>
-    >>> for q in ds.quads((None,None,None,g)):  # doctest: +SKIP
+    >>> # 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"),
@@ -1896,6 +1916,11 @@ class Dataset(ConjunctiveGraph):
             else:
                 yield s, p, o, c.identifier
 
+    def __iter__(self) -> Generator[DatasetQuad, None, None]:
+        """Iterates over all quads in the store"""
+        return self.quads((None, None, None, None))
+
+
 
 class QuotedGraph(Graph):
     """
diff --git a/rdflib/namespace/_RDF.py b/rdflib/namespace/_RDF.py
index 61bc4a58..f79d75a7 100644
--- a/rdflib/namespace/_RDF.py
+++ b/rdflib/namespace/_RDF.py
@@ -16,6 +16,7 @@ class RDF(DefinedNamespace):
     """
 
     _fail = True
+    _underscore_num = True
 
     # http://www.w3.org/1999/02/22-rdf-syntax-ns#List
     nil: URIRef  # The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it.
diff --git a/rdflib/namespace/__init__.py b/rdflib/namespace/__init__.py
index 62a2a946..23f4ec0e 100644
--- a/rdflib/namespace/__init__.py
+++ b/rdflib/namespace/__init__.py
@@ -288,71 +288,6 @@ class ClosedNamespace(Namespace):
         return dir(self)
 
 
-class _RDFNamespace(ClosedNamespace):
-    """
-    Closed namespace for RDF terms
-    """
-
-    def __new__(cls):
-        return super().__new__(
-            cls,
-            "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
-            terms=[
-                # Syntax Names
-                "RDF",
-                "Description",
-                "ID",
-                "about",
-                "parseType",
-                "resource",
-                "li",
-                "nodeID",
-                "datatype",
-                # RDF Classes
-                "Seq",
-                "Bag",
-                "Alt",
-                "Statement",
-                "Property",
-                "List",
-                "PlainLiteral",
-                # RDF Properties
-                "subject",
-                "predicate",
-                "object",
-                "type",
-                "value",
-                "first",
-                "rest",
-                # and _n where n is a non-negative integer
-                # RDF Resources
-                "nil",
-                # Added in RDF 1.1
-                "XMLLiteral",
-                "HTML",
-                "langString",
-                # Added in JSON-LD 1.1
-                "JSON",
-                "CompoundLiteral",
-                "language",
-                "direction",
-            ],
-        )
-
-    def term(self, name):
-        # Container membership properties
-        if name.startswith("_"):
-            try:
-                i = int(name[1:])
-            except ValueError:
-                pass
-            else:
-                if i > 0:
-                    return URIRef(f"{self}_{i}")
-
-        return super().term(name)
-
-
 XMLNS = Namespace("http://www.w3.org/XML/1998/namespace")
 
 
diff --git a/rdflib/plugin.py b/rdflib/plugin.py
index 1605b0bb..719c7eaf 100644
--- a/rdflib/plugin.py
+++ b/rdflib/plugin.py
@@ -205,7 +205,7 @@ register("application/n-triples", Parser, "rdflib.plugins.parsers.ntriples", "NT
 register("ntriples", Parser, "rdflib.plugins.parsers.ntriples", "NTParser")
 register("nt", Parser, "rdflib.plugins.parsers.ntriples", "NTParser")
 register("nt11", Parser, "rdflib.plugins.parsers.ntriples", "NTParser")
-register("application/json+ld", Parser, "rdflib.plugins.parsers.jsonld", "JsonLDParser")
+register("application/ld+json", Parser, "rdflib.plugins.parsers.jsonld", "JsonLDParser")
 register("json-ld", Parser, "rdflib.plugins.parsers.jsonld", "JsonLDParser")
 
 


Test patch:
diff --git a/test/test_dataset.py b/test/test_dataset.py
index 734b58cd..4860b9c3 100644
--- a/test/test_dataset.py
+++ b/test/test_dataset.py
@@ -167,7 +167,36 @@ class DatasetTestCase(unittest.TestCase):
         self.assertEqual(list(self.graph.objects(self.tarek, None)), [])
         self.assertEqual(list(g1.objects(self.tarek, None)), [self.pizza])
 
-
+    def testIter(self):
+        """PR 1382: adds __iter__ to Dataset"""
+        d = Dataset()
+        uri_a = URIRef("https://example.com/a")
+        uri_b = URIRef("https://example.com/b")
+        uri_c = URIRef("https://example.com/c")
+        uri_d = URIRef("https://example.com/d")
+
+        d.add_graph(URIRef("https://example.com/g1"))
+        d.add((uri_a, uri_b, uri_c, URIRef("https://example.com/g1")))
+        d.add((uri_a, uri_b, uri_c, URIRef(
+            "https://example.com/g1")))  # pointless addition: duplicates above
+
+        d.add_graph(URIRef("https://example.com/g2"))
+        d.add((uri_a, uri_b, uri_c, URIRef("https://example.com/g2")))
+        d.add((uri_a, uri_b, uri_d, URIRef("https://example.com/g1")))  # new, uri_d
+
+        # traditional iterator
+        i_trad = 0
+        for t in d.quads((None, None, None)):
+            i_trad += 1
+
+        # new Dataset.__iter__ iterator
+        i_new = 0
+        for t in d:
+            i_new += 1
+
+        self.assertEqual(i_new, i_trad)  # both should be 3
+        
+        
 # dynamically create classes for each registered Store
 
 pluginname = None
diff --git a/test/test_namespace.py b/test/test_namespace.py
index 23fdb1e2..76f5bbf1 100644
--- a/test/test_namespace.py
+++ b/test/test_namespace.py
@@ -241,4 +241,7 @@ class NamespacePrefixTest(unittest.TestCase):
         )
 
         ref = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
-        self.assertTrue(ref in RDF, "_RDFNamespace does not include rdf:type")
+        self.assertTrue(ref in RDF, "RDF does not include rdf:type")
+
+        ref = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#_1")
+        self.assertTrue(ref in RDF, "RDF does not include rdf:_1")