From 31c8d4c84946661c249a4e194a8888c32827a122 Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Mon, 18 Apr 2022 23:04:29 +0200 Subject: [PATCH 1/3] More type hints for `rdflib.graph` and related This patch primarily adds more type hints for `rdflib.graph`, but also adds type hints to some related modules in order to work with the new type hints for `rdflib.graph`. I'm mainly doing this as a baseline for adding type hints to `rdflib.store`. I have created type aliases to make it easier to type everything cosnsitently and to make type hints easier easier to change in the future. The type aliases are private however (i.e. `_`-prefixed) and should be kept as such for now. This patch only contains typing changes and does not change runtime behaviour. Broken off from https://github.com/RDFLib/rdflib/pull/1850 --- rdflib/compare.py | 25 +-- rdflib/graph.py | 151 ++++++++++++------ rdflib/paths.py | 10 +- rdflib/plugins/parsers/ntriples.py | 4 +- rdflib/plugins/serializers/rdfxml.py | 3 +- .../example/rdflib/plugin/parser/__init__.py | 6 +- test/test_graph/test_canonicalization.py | 9 +- test/test_graph/test_diff.py | 11 +- test/utils/earl.py | 6 +- 9 files changed, 141 insertions(+), 84 deletions(-) diff --git a/rdflib/compare.py b/rdflib/compare.py index 32f3e2fac..592836d6e 100644 --- a/rdflib/compare.py +++ b/rdflib/compare.py @@ -104,7 +104,7 @@ Union, ) -from rdflib.graph import ConjunctiveGraph, Graph, ReadOnlyGraphAggregate +from rdflib.graph import ConjunctiveGraph, Graph, ReadOnlyGraphAggregate, _TripleType from rdflib.term import BNode, IdentifiedNode, Node, URIRef if TYPE_CHECKING: @@ -281,7 +281,6 @@ def copy(self): ) -_TripleT = List[Node] _HashT = Callable[[], "HASH"] @@ -326,7 +325,9 @@ def _initial_color(self) -> List[Color]: self._neighbors[p].add(p) if len(bnodes) > 0: return [Color(list(bnodes), self.hashfunc, hash_cache=self._hash_cache)] + [ - Color([x], self.hashfunc, x, hash_cache=self._hash_cache) + # type error: List item 0 has incompatible type "Union[IdentifiedNode, Literal]"; expected "IdentifiedNode" + # type error: Argument 3 to "Color" has incompatible type "Union[IdentifiedNode, Literal]"; expected "Tuple[Tuple[Union[int, str], URIRef, Union[int, str]], ...]" + Color([x], self.hashfunc, x, hash_cache=self._hash_cache) # type: ignore[list-item, arg-type] for x in others ] else: @@ -521,7 +522,7 @@ def canonical_triples(self, stats: Optional[Stats] = None): def _canonicalize_bnodes( self, - triple: Tuple[IdentifiedNode, IdentifiedNode, Node], + triple: "_TripleType", labels: Dict[Node, str], ): for term in triple: @@ -531,7 +532,7 @@ def _canonicalize_bnodes( yield term -def to_isomorphic(graph): +def to_isomorphic(graph: Graph) -> IsomorphicGraph: if isinstance(graph, IsomorphicGraph): return graph result = IsomorphicGraph() @@ -541,7 +542,7 @@ def to_isomorphic(graph): return result -def isomorphic(graph1, graph2): +def isomorphic(graph1: Graph, graph2: Graph) -> bool: """Compare graph for equality. Uses an algorithm to compute unique hashes which takes bnodes into account. @@ -577,7 +578,9 @@ def isomorphic(graph1, graph2): return gd1 == gd2 -def to_canonical_graph(g1, stats=None): +def to_canonical_graph( + g1: Graph, stats: Optional[Stats] = None +) -> ReadOnlyGraphAggregate: """Creates a canonical, read-only graph. Creates a canonical, read-only graph where all bnode id:s are based on @@ -588,7 +591,7 @@ def to_canonical_graph(g1, stats=None): return ReadOnlyGraphAggregate([graph]) -def graph_diff(g1, g2): +def graph_diff(g1: Graph, g2: Graph) -> Tuple[Graph, Graph, Graph]: """Returns three sets of triples: "in both", "in first" and "in second".""" # bnodes have deterministic values in canonical graphs: cg1 = to_canonical_graph(g1) @@ -602,7 +605,7 @@ def graph_diff(g1, g2): _MOCK_BNODE = BNode() -def similar(g1, g2): +def similar(g1: Graph, g2: Graph): """Checks if the two graphs are "similar". Checks if the two graphs are "similar", by comparing sorted triples where @@ -615,12 +618,12 @@ def similar(g1, g2): return all(t1 == t2 for (t1, t2) in _squashed_graphs_triples(g1, g2)) -def _squashed_graphs_triples(g1, g2): +def _squashed_graphs_triples(g1: Graph, g2: Graph): for (t1, t2) in zip(sorted(_squash_graph(g1)), sorted(_squash_graph(g2))): yield t1, t2 -def _squash_graph(graph): +def _squash_graph(graph: Graph): return (_squash_bnodes(triple) for triple in graph) diff --git a/rdflib/graph.py b/rdflib/graph.py index 79ea0d769..c616a415c 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -16,6 +16,7 @@ TextIO, Tuple, Type, + TypeVar, Union, cast, overload, @@ -36,7 +37,27 @@ from rdflib.resource import Resource from rdflib.serializer import Serializer from rdflib.store import Store -from rdflib.term import BNode, Genid, IdentifiedNode, Literal, Node, RDFLibGenid, URIRef +from rdflib.term import ( + BNode, + Genid, + IdentifiedNode, + Identifier, + Literal, + Node, + RDFLibGenid, + URIRef, +) + +_SubjectType = IdentifiedNode +_PredicateType = IdentifiedNode +_ObjectType = Identifier + +_TripleType = Tuple["_SubjectType", "_PredicateType", "_ObjectType"] +_QuadType = Tuple["_SubjectType", "_PredicateType", "_ObjectType", "Graph"] +_TriplePatternType = Tuple[ + Optional["_SubjectType"], Optional["_PredicateType"], Optional["_ObjectType"] +] +_GraphT = TypeVar("_GraphT", bound="Graph") assert Literal # avoid warning assert Namespace # avoid warning @@ -356,15 +377,15 @@ def __init__( self.default_union = False @property - def store(self): + def store(self) -> Store: return self.__store @property - def identifier(self): + def identifier(self) -> Node: return self.__identifier @property - def namespace_manager(self): + def namespace_manager(self) -> NamespaceManager: """ this graph's namespace-manager """ @@ -373,7 +394,7 @@ def namespace_manager(self): return self.__namespace_manager @namespace_manager.setter - def namespace_manager(self, nm): + def namespace_manager(self, nm: NamespaceManager): self.__namespace_manager = nm def __repr__(self): @@ -424,7 +445,7 @@ def close(self, commit_pending_transaction=False): """ return self.__store.close(commit_pending_transaction=commit_pending_transaction) - def add(self, triple: Tuple[Node, Node, Node]): + def add(self, triple: "_TripleType"): """Add a triple with self as context""" s, p, o = triple assert isinstance(s, Node), "Subject %s must be an rdflib term" % (s,) @@ -433,7 +454,7 @@ def add(self, triple: Tuple[Node, Node, Node]): self.__store.add((s, p, o), self, quoted=False) return self - def addN(self, quads: Iterable[Tuple[Node, Node, Node, Any]]): # noqa: N802 + def addN(self, quads: Iterable["_QuadType"]): # noqa: N802 """Add a sequence of triple with context""" self.__store.addN( @@ -457,34 +478,40 @@ def remove(self, triple): @overload def triples( self, - triple: Tuple[ - Optional[IdentifiedNode], Optional[IdentifiedNode], Optional[Node] - ], - ) -> Iterable[Tuple[IdentifiedNode, IdentifiedNode, Node]]: + triple: "_TriplePatternType", + ) -> Generator["_TripleType", None, None]: ... @overload def triples( self, - triple: Tuple[Optional[IdentifiedNode], Path, Optional[Node]], - ) -> Iterable[Tuple[IdentifiedNode, Path, Node]]: + triple: Tuple[Optional["_SubjectType"], Path, Optional["_ObjectType"]], + ) -> Generator[Tuple["_SubjectType", "_PredicateType", "_ObjectType"], None, None]: ... @overload def triples( self, triple: Tuple[ - Optional[IdentifiedNode], Union[None, Path, IdentifiedNode], Optional[Node] + Optional["_SubjectType"], + Union[None, Path, "_PredicateType"], + Optional["_ObjectType"], ], - ) -> Iterable[Tuple[IdentifiedNode, Union[IdentifiedNode, Path], Node]]: + ) -> Generator[ + Tuple["_SubjectType", Union["_PredicateType", Path], "_ObjectType"], None, None + ]: ... def triples( self, triple: Tuple[ - Optional[IdentifiedNode], Union[None, Path, IdentifiedNode], Optional[Node] + Optional["_SubjectType"], + Union[None, Path, "_PredicateType"], + Optional["_ObjectType"], ], - ) -> Iterable[Tuple[IdentifiedNode, Union[IdentifiedNode, Path], Node]]: + ) -> Generator[ + Tuple["_SubjectType", Union["_PredicateType", Path], "_ObjectType"], None, None + ]: """Generator over the triple store Returns triples that match the given triple pattern. If triple pattern @@ -578,7 +605,7 @@ def __len__(self): """ return self.__store.__len__(context=self) - def __iter__(self): + def __iter__(self) -> Generator["_TripleType", None, None]: """Iterates over all triples in the store""" return self.triples((None, None, None)) @@ -623,20 +650,20 @@ def __gt__(self, other): def __ge__(self, other): return self > other or self == other - def __iadd__(self, other): + def __iadd__(self: "_GraphT", other: Iterable["_TripleType"]) -> "_GraphT": """Add all triples in Graph other to Graph. BNode IDs are not changed.""" self.addN((s, p, o, self) for s, p, o in other) return self - def __isub__(self, other): + def __isub__(self: "_GraphT", other: Iterable["_TripleType"]) -> "_GraphT": """Subtract all triples in Graph other from Graph. BNode IDs are not changed.""" for triple in other: self.remove(triple) return self - def __add__(self, other): + def __add__(self, other: "Graph") -> "Graph": """Set-theoretic union BNode IDs are not changed.""" try: @@ -651,7 +678,7 @@ def __add__(self, other): retval.add(y) return retval - def __mul__(self, other): + def __mul__(self, other: "Graph") -> "Graph": """Set-theoretic intersection. BNode IDs are not changed.""" try: @@ -663,7 +690,7 @@ def __mul__(self, other): retval.add(x) return retval - def __sub__(self, other): + def __sub__(self, other: "Graph") -> "Graph": """Set-theoretic difference. BNode IDs are not changed.""" try: @@ -704,10 +731,10 @@ def set(self, triple): def subjects( self, - predicate: Union[None, Path, IdentifiedNode] = None, - object: Optional[Node] = None, + predicate: Union[None, Path, "_PredicateType"] = None, + object: Optional["_ObjectType"] = None, unique: bool = False, - ) -> Iterable[IdentifiedNode]: + ) -> Generator["_SubjectType", None, None]: """A generator of (optionally unique) subjects with the given predicate and object""" if not unique: @@ -728,10 +755,10 @@ def subjects( def predicates( self, - subject: Optional[IdentifiedNode] = None, - object: Optional[Node] = None, + subject: Optional["_SubjectType"] = None, + object: Optional["_ObjectType"] = None, unique: bool = False, - ) -> Iterable[IdentifiedNode]: + ) -> Generator["_PredicateType", None, None]: """A generator of (optionally unique) predicates with the given subject and object""" if not unique: @@ -752,10 +779,10 @@ def predicates( def objects( self, - subject: Optional[IdentifiedNode] = None, - predicate: Union[None, Path, IdentifiedNode] = None, + subject: Optional["_SubjectType"] = None, + predicate: Union[None, Path, "_PredicateType"] = None, unique: bool = False, - ) -> Iterable[Node]: + ) -> Generator["_ObjectType", None, None]: """A generator of (optionally unique) objects with the given subject and predicate""" if not unique: @@ -775,8 +802,8 @@ def objects( raise def subject_predicates( - self, object: Optional[Node] = None, unique: bool = False - ) -> Generator[Tuple[IdentifiedNode, IdentifiedNode], None, None]: + self, object: Optional["_ObjectType"] = None, unique: bool = False + ) -> Generator[Tuple["_SubjectType", "_PredicateType"], None, None]: """A generator of (optionally unique) (subject, predicate) tuples for the given object""" if not unique: @@ -796,8 +823,10 @@ def subject_predicates( raise def subject_objects( - self, predicate: Union[None, Path, IdentifiedNode] = None, unique: bool = False - ) -> Generator[Tuple[IdentifiedNode, Node], None, None]: + self, + predicate: Union[None, Path, "_PredicateType"] = None, + unique: bool = False, + ) -> Generator[Tuple["_SubjectType", "_ObjectType"], None, None]: """A generator of (optionally unique) (subject, object) tuples for the given predicate""" if not unique: @@ -817,8 +846,8 @@ def subject_objects( raise def predicate_objects( - self, subject: Optional[IdentifiedNode] = None, unique: bool = False - ) -> Generator[Tuple[IdentifiedNode, Node], None, None]: + self, subject: Optional["_SubjectType"] = None, unique: bool = False + ) -> Generator[Tuple["_PredicateType", "_ObjectType"], None, None]: """A generator of (optionally unique) (predicate, object) tuples for the given subject""" if not unique: @@ -1641,10 +1670,11 @@ def __str__(self): def _spoc( self, triple_or_quad: Union[ - Tuple[Node, Node, Node, Optional[Any]], Tuple[Node, Node, Node] + Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Any]], + "_TripleType", ], default: bool = False, - ) -> Tuple[Node, Node, Node, Optional[Graph]]: + ) -> Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Graph]]: ... @overload @@ -1658,10 +1688,18 @@ def _spoc( def _spoc( self, triple_or_quad: Optional[ - Union[Tuple[Node, Node, Node, Optional[Any]], Tuple[Node, Node, Node]] + Union[ + Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Any]], + "_TripleType", + ] ], default: bool = False, - ) -> Tuple[Optional[Node], Optional[Node], Optional[Node], Optional[Graph]]: + ) -> Tuple[ + Optional["_SubjectType"], + Optional["_PredicateType"], + Optional["_ObjectType"], + Optional[Graph], + ]: """ helper method for having methods that support either triples or quads @@ -1686,7 +1724,8 @@ def __contains__(self, triple_or_quad): def add( self, triple_or_quad: Union[ - Tuple[Node, Node, Node, Optional[Any]], Tuple[Node, Node, Node] + Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Any]], + "_TripleType", ], ) -> "ConjunctiveGraph": """ @@ -1718,7 +1757,7 @@ def _graph(self, c: Optional[Union[Graph, Node, str]]) -> Optional[Graph]: else: return c - def addN(self, quads: Iterable[Tuple[Node, Node, Node, Any]]): # noqa: N802 + def addN(self, quads: Iterable["_QuadType"]): # noqa: N802 """Add a sequence of triples with context""" self.store.addN( @@ -1794,7 +1833,9 @@ def __len__(self): """Number of triples in the entire conjunctive graph""" return self.store.__len__() - def contexts(self, triple=None): + def contexts( + self, triple: Optional["_TripleType"] = None + ) -> Generator[Graph, None, None]: """Iterate over all contexts in the graph If triple is specified, iterate over all contexts the triple is in. @@ -1831,7 +1872,7 @@ def remove_context(self, context): """Removes the given context from the graph""" self.store.remove((None, None, None), context) - def context_id(self, uri, context_id=None): + def context_id(self, uri: str, context_id: Optional[str] = None) -> URIRef: """URI#context""" uri = uri.split("#", 1)[0] if context_id is None: @@ -2111,7 +2152,8 @@ def quads(self, quad): else: yield s, p, o, c.identifier - def __iter__( + # 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[Tuple[Node, URIRef, Node, Optional[IdentifiedNode]], None, None]: """Iterates over all quads in the store""" @@ -2129,7 +2171,7 @@ class QuotedGraph(Graph): def __init__(self, store, identifier): super(QuotedGraph, self).__init__(store, identifier) - def add(self, triple: Tuple[Node, Node, Node]): + def add(self, triple: "_TripleType"): """Add a triple with self as context""" s, p, o = triple assert isinstance(s, Node), "Subject %s must be an rdflib term" % (s,) @@ -2139,7 +2181,7 @@ def add(self, triple: Tuple[Node, Node, Node]): self.store.add((s, p, o), self, quoted=True) return self - def addN(self, quads: Tuple[Node, Node, Node, Any]) -> "QuotedGraph": # type: ignore[override] # noqa: N802 + def addN(self, quads: Iterable["_QuadType"]) -> "QuotedGraph": # noqa: N802 """Add a sequence of triple with context""" self.store.addN( @@ -2348,10 +2390,10 @@ def __cmp__(self, other): else: return -1 - def __iadd__(self, other): + def __iadd__(self: "_GraphT", other: Iterable["_TripleType"]) -> "_GraphT": raise ModificationException() - def __isub__(self, other): + def __isub__(self: "_GraphT", other: Iterable["_TripleType"]) -> "_GraphT": raise ModificationException() # Conv. methods @@ -2442,7 +2484,10 @@ def reset(self): def add( self, - triple_or_quad: Union[Tuple[Node, Node, Node], Tuple[Node, Node, Node, Any]], + triple_or_quad: Union[ + "_TripleType", + "_QuadType", + ], ) -> "BatchAddGraph": """ Add a triple to the buffer @@ -2459,7 +2504,7 @@ def add( self.batch.append(triple_or_quad) return self - def addN(self, quads: Iterable[Tuple[Node, Node, Node, Any]]): # noqa: N802 + def addN(self, quads: Iterable["_QuadType"]): # noqa: N802 if self.__batch_addn: for q in quads: self.add(q) diff --git a/rdflib/paths.py b/rdflib/paths.py index 8e2c60f19..0aabd19c8 100644 --- a/rdflib/paths.py +++ b/rdflib/paths.py @@ -184,10 +184,10 @@ from functools import total_ordering from typing import TYPE_CHECKING, Callable, Iterator, Optional, Tuple, Union -from rdflib.term import IdentifiedNode, Node, URIRef +from rdflib.term import Node, URIRef if TYPE_CHECKING: - from rdflib import Graph + from rdflib.graph import Graph, _ObjectType, _SubjectType # property paths @@ -209,9 +209,9 @@ class Path(object): def eval( self, graph: "Graph", - subj: Optional[IdentifiedNode] = None, - obj: Optional[Node] = None, - ) -> Iterator[Tuple[IdentifiedNode, Node]]: + subj: Optional["_SubjectType"] = None, + obj: Optional["_ObjectType"] = None, + ) -> Iterator[Tuple["_SubjectType", "_ObjectType"]]: raise NotImplementedError() def __lt__(self, other): diff --git a/rdflib/plugins/parsers/ntriples.py b/rdflib/plugins/parsers/ntriples.py index 42b4940e0..5057f14c2 100644 --- a/rdflib/plugins/parsers/ntriples.py +++ b/rdflib/plugins/parsers/ntriples.py @@ -19,7 +19,7 @@ from rdflib.term import URIRef as URI if TYPE_CHECKING: - from rdflib.graph import Graph + from rdflib.graph import Graph, _ObjectType, _PredicateType, _SubjectType __all__ = ["unquote", "uriquote", "W3CNTriplesParser", "NTGraphSink", "NTParser"] @@ -306,7 +306,7 @@ class NTGraphSink(object): def __init__(self, graph: "Graph"): self.g = graph - def triple(self, s: Node, p: Node, o: Node): + def triple(self, s: "_SubjectType", p: "_PredicateType", o: "_ObjectType"): self.g.add((s, p, o)) diff --git a/rdflib/plugins/serializers/rdfxml.py b/rdflib/plugins/serializers/rdfxml.py index 587f1f164..f467bfd9a 100644 --- a/rdflib/plugins/serializers/rdfxml.py +++ b/rdflib/plugins/serializers/rdfxml.py @@ -190,7 +190,8 @@ def serialize( ) for predicate in possible: - prefix, namespace, local = nm.compute_qname_strict(predicate) + # type error: Argument 1 to "compute_qname_strict" of "NamespaceManager" has incompatible type "Node"; expected "str" + prefix, namespace, local = nm.compute_qname_strict(predicate) # type: ignore[arg-type] namespaces[prefix] = namespace namespaces["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" diff --git a/test/plugins/parser/example/rdflib/plugin/parser/__init__.py b/test/plugins/parser/example/rdflib/plugin/parser/__init__.py index 6aae60256..a6aefa291 100644 --- a/test/plugins/parser/example/rdflib/plugin/parser/__init__.py +++ b/test/plugins/parser/example/rdflib/plugin/parser/__init__.py @@ -6,7 +6,7 @@ from rdflib.graph import Graph from rdflib.namespace import Namespace from rdflib.parser import InputSource - from rdflib.term import Identifier + from rdflib.term import URIRef class ExampleParser(Parser): @@ -22,7 +22,9 @@ def namespace(cls) -> "Namespace": return Namespace("example:rdflib:plugin:parser:") @classmethod - def constant_output(cls) -> Set[Tuple["Identifier", "Identifier", "Identifier"]]: + def constant_output( + cls, + ) -> Set[Tuple["URIRef", "URIRef", "URIRef"]]: return {(cls.namespace().subj, cls.namespace().pred, cls.namespace().obj)} diff --git a/test/test_graph/test_canonicalization.py b/test/test_graph/test_canonicalization.py index 0c69d202b..330692674 100644 --- a/test/test_graph/test_canonicalization.py +++ b/test/test_graph/test_canonicalization.py @@ -1,8 +1,9 @@ import unittest from collections import Counter from io import StringIO +from test.testutils import GraphHelper from test.utils import GraphHelper -from typing import Set, Tuple +from typing import TYPE_CHECKING, Set import pytest @@ -13,6 +14,9 @@ from rdflib.plugins.stores.memory import Memory from rdflib.term import Node +if TYPE_CHECKING: + from rdflib.graph import _TripleType + def get_digest_value(rdf, mimetype): graph = Graph() @@ -516,8 +520,7 @@ def test_issue725_collapsing_bnodes_2(): ), "canonicalization changed node position counts" -_Triple = Tuple[Node, Node, Node] -_TripleSet = Set[_Triple] +_TripleSet = Set["_TripleType"] class TestConsistency(unittest.TestCase): diff --git a/test/test_graph/test_diff.py b/test/test_graph/test_diff.py index a6e22f0bc..7ed225fc6 100644 --- a/test/test_graph/test_diff.py +++ b/test/test_graph/test_diff.py @@ -1,19 +1,22 @@ import unittest +from test.testutils import GraphHelper from test.utils import GraphHelper -from typing import Set, Tuple +from typing import TYPE_CHECKING, Set from unittest.case import expectedFailure import rdflib from rdflib import Graph from rdflib.compare import graph_diff from rdflib.namespace import FOAF, RDF -from rdflib.term import BNode, Literal, Node +from rdflib.term import BNode, Literal + +if TYPE_CHECKING: + from rdflib.graph import _TripleType """Test for graph_diff - much more extensive testing would certainly be possible""" -_TripleT = Tuple[Node, Node, Node] -_TripleSetT = Set[_TripleT] +_TripleSetT = Set["_TripleType"] class TestDiff(unittest.TestCase): diff --git a/test/utils/earl.py b/test/utils/earl.py index dd8b75388..d8a5d92a3 100644 --- a/test/utils/earl.py +++ b/test/utils/earl.py @@ -13,7 +13,7 @@ from rdflib import RDF, BNode, Graph, Literal, URIRef from rdflib.namespace import DC, DOAP, FOAF -from rdflib.term import Node +from rdflib.term import IdentifiedNode, Node if TYPE_CHECKING: from _pytest.main import Session @@ -40,8 +40,8 @@ def __init__( graph.bind("doap", DOAP) graph.bind("dc", DC) - self.asserter: Node - asserter: Node + self.asserter: IdentifiedNode + asserter: IdentifiedNode if asserter_uri is not None or asserter_homepage is not None: # cast to remove Optional because mypy is not smart enough to # figure out that it won't be optional. From 434dc338cd32fde155f743a1f86b7e649904dad1 Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Tue, 19 Apr 2022 21:01:52 +0200 Subject: [PATCH 2/3] Changes from review: - Fixed second overload of `_TripleType` to return `Path` instead of `_PredicateType`. --- rdflib/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdflib/graph.py b/rdflib/graph.py index c616a415c..a06b51cc4 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -486,7 +486,7 @@ def triples( def triples( self, triple: Tuple[Optional["_SubjectType"], Path, Optional["_ObjectType"]], - ) -> Generator[Tuple["_SubjectType", "_PredicateType", "_ObjectType"], None, None]: + ) -> Generator[Tuple["_SubjectType", Path, "_ObjectType"], None, None]: ... @overload From a79650ddb4e3173605bfcaf74645a77dbf304c14 Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Sat, 21 May 2022 17:24:19 +0200 Subject: [PATCH 3/3] Additional fixes Changed type hints based on more information discovered in mean time. --- rdflib/graph.py | 95 +++++++++++++++++------- rdflib/plugins/parsers/ntriples.py | 2 +- rdflib/plugins/serializers/rdfxml.py | 6 +- rdflib/util.py | 17 ++--- test/test_graph/test_canonicalization.py | 2 - test/test_graph/test_diff.py | 1 - test/test_namespace/test_namespace.py | 2 +- test/test_roundtrip.py | 6 +- test/test_typing.py | 7 +- test/utils/__init__.py | 9 ++- 10 files changed, 97 insertions(+), 50 deletions(-) diff --git a/rdflib/graph.py b/rdflib/graph.py index a06b51cc4..97b029bf9 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -37,26 +37,29 @@ from rdflib.resource import Resource from rdflib.serializer import Serializer from rdflib.store import Store -from rdflib.term import ( - BNode, - Genid, - IdentifiedNode, - Identifier, - Literal, - Node, - RDFLibGenid, - URIRef, -) +from rdflib.term import BNode, Genid, IdentifiedNode, Literal, Node, RDFLibGenid, URIRef -_SubjectType = IdentifiedNode -_PredicateType = IdentifiedNode -_ObjectType = Identifier +_SubjectType = Node +_PredicateType = Node +_ObjectType = Node _TripleType = Tuple["_SubjectType", "_PredicateType", "_ObjectType"] _QuadType = Tuple["_SubjectType", "_PredicateType", "_ObjectType", "Graph"] +_OptionalQuadType = Tuple[ + "_SubjectType", "_PredicateType", "_ObjectType", Optional["Graph"] +] +_OptionalIdentifiedQuadType = Tuple[ + "_SubjectType", "_PredicateType", "_ObjectType", Optional["Node"] +] _TriplePatternType = Tuple[ Optional["_SubjectType"], Optional["_PredicateType"], Optional["_ObjectType"] ] +_QuadPatternType = Tuple[ + Optional["_SubjectType"], + Optional["_PredicateType"], + Optional["_ObjectType"], + Optional["Graph"], +] _GraphT = TypeVar("_GraphT", bound="Graph") assert Literal # avoid warning @@ -522,7 +525,9 @@ def triples( for _s, _o in p.eval(self, s, o): yield _s, p, _o else: - for (_s, _p, _o), cg in self.__store.triples((s, p, o), context=self): + # type error: Argument 1 to "triples" of "Store" has incompatible type "Tuple[Optional[Node], Optional[Node], Optional[Node]]"; expected "Tuple[Optional[IdentifiedNode], Optional[IdentifiedNode], Optional[Node]]" + # NOTE on type error: This is because the store typing is too narrow, willbe fixed in subsequent PR. + for (_s, _p, _o), cg in self.__store.triples((s, p, o), context=self): # type: ignore [arg-type] yield _s, _p, _o def __getitem__(self, item): @@ -1670,11 +1675,21 @@ def __str__(self): def _spoc( self, triple_or_quad: Union[ - Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Any]], - "_TripleType", + Tuple[ + Optional["_SubjectType"], + Optional["_PredicateType"], + Optional["_ObjectType"], + Optional[Any], + ], + "_TriplePatternType", ], default: bool = False, - ) -> Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Graph]]: + ) -> Tuple[ + Optional["_SubjectType"], + Optional["_PredicateType"], + Optional["_ObjectType"], + Optional[Graph], + ]: ... @overload @@ -1689,8 +1704,13 @@ def _spoc( self, triple_or_quad: Optional[ Union[ - Tuple["_SubjectType", "_PredicateType", "_ObjectType", Optional[Any]], - "_TripleType", + Tuple[ + Optional["_SubjectType"], + Optional["_PredicateType"], + Optional["_ObjectType"], + Optional[Any], + ], + "_TriplePatternType", ] ], default: bool = False, @@ -1738,7 +1758,8 @@ def add( _assertnode(s, p, o) - self.store.add((s, p, o), context=c, quoted=False) + # 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 @@ -1808,14 +1829,24 @@ def triples(self, triple_or_quad, context=None): for (s, p, o), cg in self.store.triples((s, p, o), context=context): yield s, p, o - def quads(self, triple_or_quad=None): + def quads( + self, + triple_or_quad: Union[ + "_TriplePatternType", + "_QuadPatternType", + 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): + # type error: Argument 1 to "triples" of "Store" has incompatible type "Tuple[Optional[Node], Optional[Node], Optional[Node]]"; expected "Tuple[Optional[IdentifiedNode], Optional[IdentifiedNode], Optional[Node]]" + # NOTE on type error: This is because the store typing is too narrow, willbe fixed in subsequent PR. + for (s, p, o), cg in self.store.triples((s, p, o), context=c): # type: ignore[arg-type] for ctx in cg: - yield s, p, o, ctx + # type error: Incompatible types in "yield" (actual type "Tuple[Optional[Node], Optional[Node], Optional[Node], Any]", expected type "Tuple[Node, Node, Node, Optional[Graph]]") + yield s, p, o, ctx # type: ignore[misc] def triples_choices(self, triple, context=None): """Iterate over all the triples in the entire conjunctive graph""" @@ -2145,17 +2176,27 @@ def contexts(self, triple=None): graphs = contexts - def quads(self, quad): + # 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: Union[ + "_TriplePatternType", + "_QuadPatternType", + None, + ] = None, + ) -> Generator[_OptionalIdentifiedQuadType, None, None]: for s, p, o, c in super(Dataset, self).quads(quad): - if c.identifier == self.default_context: + # 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: - yield s, p, o, c.identifier + # 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[Tuple[Node, URIRef, Node, Optional[IdentifiedNode]], None, None]: + ) -> Generator[_OptionalIdentifiedQuadType, None, None]: """Iterates over all quads in the store""" return self.quads((None, None, None, None)) diff --git a/rdflib/plugins/parsers/ntriples.py b/rdflib/plugins/parsers/ntriples.py index 5057f14c2..2a199f637 100644 --- a/rdflib/plugins/parsers/ntriples.py +++ b/rdflib/plugins/parsers/ntriples.py @@ -15,7 +15,7 @@ from rdflib.exceptions import ParserError as ParseError from rdflib.parser import InputSource, Parser from rdflib.term import BNode as bNode -from rdflib.term import Literal, Node +from rdflib.term import Literal from rdflib.term import URIRef as URI if TYPE_CHECKING: diff --git a/rdflib/plugins/serializers/rdfxml.py b/rdflib/plugins/serializers/rdfxml.py index f467bfd9a..0308b5164 100644 --- a/rdflib/plugins/serializers/rdfxml.py +++ b/rdflib/plugins/serializers/rdfxml.py @@ -207,7 +207,8 @@ def serialize( subject: IdentifiedNode # Write out subjects that can not be inline - for subject in store.subjects(): + # type error: Incompatible types in assignment (expression has type "Node", variable has type "IdentifiedNode") + for subject in store.subjects(): # type: ignore[assignment] if (None, None, subject) in store: if (subject, None, subject) in store: self.subject(subject, 1) @@ -218,7 +219,8 @@ def serialize( # write out BNodes last (to ensure they can be inlined where possible) bnodes = set() - for subject in store.subjects(): + # type error: Incompatible types in assignment (expression has type "Node", variable has type "IdentifiedNode") + for subject in store.subjects(): # type: ignore[assignment] if isinstance(subject, BNode): bnodes.add(subject) continue diff --git a/rdflib/util.py b/rdflib/util.py index 328a87430..c3d04065c 100644 --- a/rdflib/util.py +++ b/rdflib/util.py @@ -29,7 +29,7 @@ TYPE_CHECKING, Any, Callable, - Iterable, + Iterator, List, Optional, Set, @@ -41,7 +41,7 @@ import rdflib.graph # avoid circular dependency from rdflib.compat import sign from rdflib.namespace import XSD, Namespace, NamespaceManager -from rdflib.term import BNode, IdentifiedNode, Literal, Node, URIRef +from rdflib.term import BNode, Literal, Node, URIRef if TYPE_CHECKING: from rdflib.graph import Graph @@ -409,13 +409,13 @@ def find_roots( def get_tree( graph: "Graph", - root: "IdentifiedNode", + root: "Node", prop: "URIRef", - mapper: Callable[["IdentifiedNode"], "IdentifiedNode"] = lambda x: x, + mapper: Callable[["Node"], "Node"] = lambda x: x, sortkey: Optional[Callable[[Any], Any]] = None, - done: Optional[Set["IdentifiedNode"]] = None, + done: Optional[Set["Node"]] = None, dir: str = "down", -) -> Optional[Tuple[IdentifiedNode, List[Any]]]: +) -> Optional[Tuple[Node, List[Any]]]: """ Return a nested list/tuple structure representing the tree built by the transitive property given, starting from the root given @@ -442,12 +442,11 @@ def get_tree( done.add(root) tree = [] - branches: Iterable[IdentifiedNode] + branches: Iterator[Node] if dir == "down": branches = graph.subjects(prop, root) else: - # type error: Incompatible types in assignment (expression has type "Iterable[Node]", variable has type "Iterable[IdentifiedNode]") - branches = graph.objects(root, prop) # type: ignore[assignment] + branches = graph.objects(root, prop) for branch in branches: t = get_tree(graph, branch, prop, mapper, sortkey, done, dir) diff --git a/test/test_graph/test_canonicalization.py b/test/test_graph/test_canonicalization.py index 330692674..88410fe0a 100644 --- a/test/test_graph/test_canonicalization.py +++ b/test/test_graph/test_canonicalization.py @@ -1,7 +1,6 @@ import unittest from collections import Counter from io import StringIO -from test.testutils import GraphHelper from test.utils import GraphHelper from typing import TYPE_CHECKING, Set @@ -12,7 +11,6 @@ from rdflib.compare import to_canonical_graph, to_isomorphic from rdflib.namespace import FOAF from rdflib.plugins.stores.memory import Memory -from rdflib.term import Node if TYPE_CHECKING: from rdflib.graph import _TripleType diff --git a/test/test_graph/test_diff.py b/test/test_graph/test_diff.py index 7ed225fc6..4839fcb4b 100644 --- a/test/test_graph/test_diff.py +++ b/test/test_graph/test_diff.py @@ -1,5 +1,4 @@ import unittest -from test.testutils import GraphHelper from test.utils import GraphHelper from typing import TYPE_CHECKING, Set from unittest.case import expectedFailure diff --git a/test/test_namespace/test_namespace.py b/test/test_namespace/test_namespace.py index 9c680876a..45c9a0693 100644 --- a/test/test_namespace/test_namespace.py +++ b/test/test_namespace/test_namespace.py @@ -279,7 +279,7 @@ def test_expand_curie_exception_messages(self) -> None: assert str(e.value) == "Argument must be a string, not BNode." with pytest.raises(TypeError) as e: - assert g.namespace_manager.expand_curie(Graph()) is None + assert g.namespace_manager.expand_curie(Graph()) is None # type: ignore[arg-type] assert str(e.value) == "Argument must be a string, not Graph." @pytest.mark.parametrize( diff --git a/test/test_roundtrip.py b/test/test_roundtrip.py index 6a3f492e2..632b3add1 100644 --- a/test/test_roundtrip.py +++ b/test/test_roundtrip.py @@ -268,10 +268,12 @@ def roundtrip( # # So we have to scrub the literals' string datatype declarations... for c in g2.contexts(): - for s, p, o in c.triples((None, None, None)): + # type error: Incompatible types in assignment (expression has type "Node", variable has type "str") + for s, p, o in c.triples((None, None, None)): # type: ignore[assignment] if type(o) == rdflib.Literal and o.datatype == XSD.string: c.remove((s, p, o)) - c.add((s, p, rdflib.Literal(str(o)))) + # type error: Argument 1 to "add" of "Graph" has incompatible type "Tuple[str, Node, Literal]"; expected "Tuple[Node, Node, Node]" + c.add((s, p, rdflib.Literal(str(o)))) # type: ignore[arg-type] if logger.isEnabledFor(logging.DEBUG): both, first, second = rdflib.compare.graph_diff(g1, g2) diff --git a/test/test_typing.py b/test/test_typing.py index 3fd58a3a3..2ee06d2a4 100644 --- a/test/test_typing.py +++ b/test/test_typing.py @@ -22,9 +22,12 @@ from typing import Set, Tuple +import rdflib + # TODO Bug - rdflib.plugins.sparql.prepareQuery() will run fine if this # test is run, but mypy can't tell the symbol is exposed. import rdflib.plugins.sparql.processor +from rdflib.term import Node def test_rdflib_query_exercise() -> None: @@ -56,8 +59,8 @@ def test_rdflib_query_exercise() -> None: graph.add((kb_https_uriref, predicate_q, literal_two)) graph.add((kb_bnode, predicate_p, literal_one)) - expected_nodes_using_predicate_q: Set[rdflib.IdentifiedNode] = {kb_https_uriref} - computed_nodes_using_predicate_q: Set[rdflib.IdentifiedNode] = set() + expected_nodes_using_predicate_q: Set[Node] = {kb_https_uriref} + computed_nodes_using_predicate_q: Set[Node] = set() for triple in graph.triples((None, predicate_q, None)): computed_nodes_using_predicate_q.add(triple[0]) assert expected_nodes_using_predicate_q == computed_nodes_using_predicate_q diff --git a/test/utils/__init__.py b/test/utils/__init__.py index 47270f765..56af5e5ff 100644 --- a/test/utils/__init__.py +++ b/test/utils/__init__.py @@ -169,12 +169,15 @@ def quad_set( """ result: GHQuadSet = set() for sn, pn, on, gn in graph.quads((None, None, None, None)): + gn_id: Identifier if isinstance(graph, Dataset): - assert isinstance(gn, Identifier) - gn_id = gn + # type error: Subclass of "Graph" and "Identifier" cannot exist: would have incompatible method signatures + assert isinstance(gn, Identifier) # type: ignore[unreachable] + gn_id = gn # type: ignore[unreachable] elif isinstance(graph, ConjunctiveGraph): assert isinstance(gn, Graph) - gn_id = gn.identifier + # type error: Incompatible types in assignment (expression has type "Node", variable has type "Identifier") + gn_id = gn.identifier # type: ignore[assignment] else: raise ValueError(f"invalid graph type {type(graph)}: {graph!r}") s, p, o = cls.nodes((sn, pn, on), exclude_blanks)