Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add type hints to rdflib.graph #2080

Merged
merged 1 commit into from
Aug 23, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -166,8 +166,10 @@ and will be removed for release.
<!-- -->

- Added type hints.
[PR #2057](https://github.com/RDFLib/rdflib/pull/2057).
- `rdflib.store` and builtin stores have mostly complete type hints.
[PR #2057](https://github.com/RDFLib/rdflib/pull/2057).
- `rdflib.graph` have mostly complete type hints.
[PR #2080](https://github.com/RDFLib/rdflib/pull/2080).

<!-- -->
<!-- -->
22 changes: 15 additions & 7 deletions devtools/diffrtpy.py
Original file line number Diff line number Diff line change
@@ -32,8 +32,16 @@
from strip_hints import strip_string_to_string


def clean_python(code: str) -> str:
code = strip_string_to_string(code, to_empty=True, strip_nl=True)
def clean_python(input: Path) -> str:
code = input.read_text()
try:
code = strip_string_to_string(code, to_empty=True, strip_nl=True)
except Exception:
logging.warning(
"failed to strip type hints from %s, falling back to using with type hints",
input,
)
code = code
code = python_minifier.minify(
code,
remove_annotations=True,
@@ -106,12 +114,12 @@ def handle(self, parse_result: argparse.Namespace) -> None:
"base = %s, lhs_file = %s, rhs_file = %s", base, lhs_file, rhs_file
)

lhs_file_content = lhs_file.read_text()
rhs_file_content = rhs_file.read_text()

if lhs_file.name.endswith(".py") and rhs_file.name.endswith(".py"):
lhs_file_content = clean_python(lhs_file_content)
rhs_file_content = clean_python(rhs_file_content)
lhs_file_content = clean_python(lhs_file)
rhs_file_content = clean_python(rhs_file)
else:
lhs_file_content = lhs_file.read_text()
rhs_file_content = rhs_file.read_text()

lhs_file_lines = lhs_file_content.splitlines(keepends=True)
rhs_file_lines = rhs_file_content.splitlines(keepends=True)
19 changes: 15 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -264,6 +264,7 @@ def find_version(filename):
("py:class", "importlib.metadata.EntryPoint"),
("py:class", "xml.dom.minidom.Document"),
("py:class", "xml.dom.minidom.DocumentFragment"),
("py:class", "isodate.duration.Duration"),
# sphinx-autodoc-typehints has some issues with TypeVars.
# https://github.com/tox-dev/sphinx-autodoc-typehints/issues/39
("py:class", "rdflib.plugin.PluginT"),
@@ -282,13 +283,23 @@ def find_version(filename):
if sys.version_info < (3, 9):
nitpick_ignore.extend(
[
("py:class", "_TriplePatternType"),
("py:class", "_TripleType"),
("py:class", "_ContextIdentifierType"),
("py:class", "_ContextType"),
("py:class", "_GraphT"),
("py:class", "_NamespaceSetString"),
("py:class", "_ObjectType"),
("py:class", "_PredicateType"),
("py:class", "_QuadSelectorType"),
("py:class", "_SubjectType"),
("py:class", "_ContextType"),
("py:class", "_ContextIdentifierType"),
("py:class", "_TripleOrPathTripleType"),
("py:class", "_TripleOrQuadPathPatternType"),
("py:class", "_TripleOrQuadPatternType"),
("py:class", "_TriplePathPatternType"),
("py:class", "_TriplePathType"),
("py:class", "_TriplePatternType"),
("py:class", "_TripleSelectorType"),
("py:class", "_TripleType"),
("py:class", "_TripleOrTriplePathType"),
("py:class", "TextIO"),
]
)
840 changes: 581 additions & 259 deletions rdflib/graph.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion rdflib/plugins/parsers/hext.py
Original file line number Diff line number Diff line change
@@ -66,7 +66,8 @@ def _parse_hextuple(self, cg: ConjunctiveGraph, tup: List[Union[str, None]]):
# 6 - context
if tup[5] is not None:
c = URIRef(tup[5])
cg.add((s, p, o, c))
# type error: Argument 1 to "add" of "ConjunctiveGraph" has incompatible type "Tuple[Union[URIRef, BNode], URIRef, Union[URIRef, BNode, Literal], URIRef]"; expected "Union[Tuple[Node, Node, Node], Tuple[Node, Node, Node, Optional[Graph]]]"
cg.add((s, p, o, c)) # type: ignore[arg-type]
else:
cg.add((s, p, o))

27 changes: 14 additions & 13 deletions rdflib/plugins/stores/memory.py
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
Dict,
Generator,
Iterator,
Mapping,
Optional,
Set,
Tuple,
@@ -244,9 +245,9 @@ def __contexts(self) -> Generator["_ContextType", None, None]:
def query( # type: ignore[return]
self,
query: Union["Query", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier", # noqa: N803
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: "str", # noqa: N803
**kwargs: Any,
) -> "Result":
super(SimpleMemory, self).query(
@@ -256,9 +257,9 @@ def query( # type: ignore[return]
def update(
self,
update: Union["Update", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier", # noqa: N803
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: "str", # noqa: N803
**kwargs: Any,
) -> None:
super(SimpleMemory, self).update(
@@ -720,19 +721,19 @@ def __contexts(
def query( # type: ignore[return]
self,
query: Union["Query", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier",
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: "str",
**kwargs,
) -> "Result":
super(Memory, self).query(query, initNs, initBindings, queryGraph, **kwargs)

def update(
self,
update: Union["Update", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier",
update: Union["Update", Any],
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: "str",
**kwargs,
) -> None:
super(Memory, self).update(update, initNs, initBindings, queryGraph, **kwargs)
15 changes: 8 additions & 7 deletions rdflib/plugins/stores/sparqlstore.py
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
Iterable,
Iterator,
List,
Mapping,
Optional,
Tuple,
Union,
@@ -191,7 +192,7 @@ def remove( # type: ignore[override]
def update( # type: ignore[override]
self,
query: Union["Update", str],
initNs: Dict[str, str] = {}, # noqa: N803
initNs: Dict[str, Any] = {}, # noqa: N803
initBindings: Dict["Variable", "Identifier"] = {},
queryGraph: "Identifier" = None,
DEBUG: bool = False,
@@ -203,7 +204,7 @@ def _query(self, *args: Any, **kwargs: Any) -> "Result":

return super(SPARQLStore, self).query(*args, **kwargs)

def _inject_prefixes(self, query: str, extra_bindings: Dict[str, str]) -> str:
def _inject_prefixes(self, query: str, extra_bindings: Mapping[str, Any]) -> str:
bindings = set(list(self.nsBindings.items()) + list(extra_bindings.items()))
if not bindings:
return query
@@ -220,9 +221,9 @@ def _inject_prefixes(self, query: str, extra_bindings: Dict[str, str]) -> str:
def query( # type: ignore[override]
self,
query: Union["Query", str],
initNs: Optional[Dict[str, str]] = None, # noqa: N803
initBindings: Optional[Dict["Variable", "Identifier"]] = None,
queryGraph: Optional["Identifier"] = None,
initNs: Optional[Mapping[str, Any]] = None, # noqa: N803
initBindings: Optional[Mapping["Variable", "Identifier"]] = None,
queryGraph: Optional["str"] = None,
DEBUG: bool = False,
) -> "Result":
self.debug = DEBUG
@@ -821,9 +822,9 @@ def _update(self, update):
def update( # type: ignore[override]
self,
query: Union["Update", str],
initNs: Dict[str, str] = {}, # noqa: N803
initNs: Dict[str, Any] = {}, # noqa: N803
initBindings: Dict["Variable", "Identifier"] = {},
queryGraph: Optional["Identifier"] = None,
queryGraph: Optional[str] = None,
DEBUG: bool = False,
):
"""
67 changes: 13 additions & 54 deletions rdflib/store.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import pickle
from io import BytesIO
from typing import (
@@ -12,7 +14,6 @@
Optional,
Tuple,
Union,
overload,
)

from rdflib.events import Dispatcher, Event
@@ -278,59 +279,17 @@ def remove(
"""Remove the set of triples matching the pattern from the store"""
self.dispatcher.dispatch(TripleRemovedEvent(triple=triple, context=context))

@overload
def triples_choices(
self,
triple: Tuple[List["_SubjectType"], "_PredicateType", "_ObjectType"],
context: Optional["_ContextType"] = None,
) -> Generator[
Tuple["_TripleType", Iterator[Optional["_ContextType"]]],
None,
None,
]:
...

@overload
def triples_choices(
self,
triple: Tuple["_SubjectType", List["_PredicateType"], "_ObjectType"],
context: Optional["_ContextType"] = None,
) -> Generator[
Tuple[
Tuple["_SubjectType", "_PredicateType", "_ObjectType"],
Iterator[Optional["_ContextType"]],
],
None,
None,
]:
...

@overload
def triples_choices(
self,
triple: Tuple["_SubjectType", "_PredicateType", List["_ObjectType"]],
context: Optional["_ContextType"] = None,
) -> Generator[
Tuple[
Tuple["_SubjectType", "_PredicateType", "_ObjectType"],
Iterator[Optional["_ContextType"]],
],
None,
None,
]:
...

def triples_choices(
self,
triple: Tuple[
Union["_SubjectType", List["_SubjectType"]],
Union["_PredicateType", List["_PredicateType"]],
Union["_ObjectType", List["_ObjectType"]],
triple: Union[
Tuple[List["_SubjectType"], "_PredicateType", "_ObjectType"],
Tuple["_SubjectType", List["_PredicateType"], "_ObjectType"],
Tuple["_SubjectType", "_PredicateType", List["_ObjectType"]],
],
context: Optional["_ContextType"] = None,
) -> Generator[
Tuple[
Tuple["_SubjectType", "_PredicateType", "_ObjectType"],
_TripleType,
Iterator[Optional["_ContextType"]],
],
None,
@@ -430,9 +389,9 @@ def contexts(
def query(
self,
query: Union["Query", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier", # noqa: N803
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: str, # noqa: N803
**kwargs: Any,
) -> "Result":
"""
@@ -453,9 +412,9 @@ def query(
def update(
self,
update: Union["Update", str],
initNs: Dict[str, str], # noqa: N803
initBindings: Dict["Variable", "Identifier"], # noqa: N803
queryGraph: "Identifier", # noqa: N803
initNs: Mapping[str, Any], # noqa: N803
initBindings: Mapping["Variable", "Identifier"], # noqa: N803
queryGraph: str, # noqa: N803
**kwargs: Any,
) -> None:
"""
3 changes: 2 additions & 1 deletion test/test_roundtrip.py
Original file line number Diff line number Diff line change
@@ -271,7 +271,8 @@ def roundtrip(
# 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))
# type error: Argument 1 to "remove" of "Graph" has incompatible type "Tuple[str, Node, Literal]"; expected "Tuple[Optional[Node], Optional[Node], Optional[Node]]"
c.remove((s, p, o)) # type: ignore[arg-type]
# 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]

3 changes: 2 additions & 1 deletion test/test_serializers/test_serializer.py
Original file line number Diff line number Diff line change
@@ -60,7 +60,8 @@ def test_rdf_type(format: str, tuple_index: int, is_keyword: bool) -> None:
nodes = [NS.subj, NS.pred, NS.obj, NS.graph]
nodes[tuple_index] = RDF.type
quad = cast(Tuple[URIRef, URIRef, URIRef, URIRef], tuple(nodes))
graph.add(quad)
# type error: Argument 1 to "add" of "ConjunctiveGraph" has incompatible type "Tuple[URIRef, URIRef, URIRef, URIRef]"; expected "Union[Tuple[Node, Node, Node], Tuple[Node, Node, Node, Optional[Graph]]]"
graph.add(quad) # type: ignore[arg-type]
data = graph.serialize(format=format)
logging.info("data = %s", data)
assert NS in data
2 changes: 2 additions & 0 deletions test/utils/dawg_manifest.py
Original file line number Diff line number Diff line change
@@ -151,6 +151,7 @@ def from_sources(
def included(self) -> Generator["Manifest", None, None]:
for includes in self.graph.objects(self.identifier, MF.include):
for include in self.graph.items(includes):
assert isinstance(include, str)
include_local_path = self.uri_mapper.to_local_path(include)
yield from Manifest.from_sources(
self.uri_mapper,
@@ -166,6 +167,7 @@ def entires(
) -> Generator["ManifestEntryT", None, None]:
for entries in self.graph.objects(self.identifier, MF.entries):
for entry_iri in self.graph.items(entries):
assert isinstance(entry_iri, URIRef)
entry = entry_type(self, entry_iri)
if exclude is not None and entry.check_filters(exclude):
continue
28 changes: 18 additions & 10 deletions test/utils/manifest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import logging
from test.utils.namespace import DAWGT, MF, QT, RDFT, UT
from typing import Iterable, List, NamedTuple, Optional, Tuple, Union, cast
@@ -7,8 +9,10 @@

logger = logging.getLogger(__name__)

ResultType = Union[Identifier, Tuple[Identifier, List[Tuple[Identifier, Identifier]]]]
GraphDataType = Union[List[Identifier], List[Tuple[Identifier, Identifier]]]
ResultType = Union[
Identifier, Tuple[Optional[Node], List[Tuple[Optional[Node], Optional[Node]]]]
]
GraphDataType = Union[List[Optional[Node]], List[Tuple[Optional[Node], Optional[Node]]]]


class RDFTest(NamedTuple):
@@ -22,7 +26,9 @@ class RDFTest(NamedTuple):
syntax: bool


def read_manifest(f, base=None, legacy=False) -> Iterable[Tuple[Node, URIRef, RDFTest]]:
def read_manifest(f, base=None, legacy=False) -> Iterable[Tuple[Node, Node, RDFTest]]:
"""read a manifest file"""

def _str(x):
if x is not None:
return str(x)
@@ -39,7 +45,7 @@ def _str(x):
yield x

for col in g.objects(m, MF.entries):
e: URIRef
e: Node
for e in g.items(col):

approved = (
@@ -81,22 +87,22 @@ def _str(x):
# NOTE: Casting to identifier because g.objects return Node
# but should probably return identifier instead.
graphdata = list(
cast(Iterable[Identifier], g.objects(a, QT.graphData))
cast(Iterable[Optional[Node]], g.objects(a, QT.graphData))
)
res = g.value(e, MF.result)
res = cast(Optional[ResultType], g.value(e, MF.result))
elif _type in (MF.UpdateEvaluationTest, UT.UpdateEvaluationTest):
a = g.value(e, MF.action)
query = g.value(a, UT.request)
data = g.value(a, UT.data)
graphdata = cast(List[Tuple[Identifier, Identifier]], [])
graphdata = cast(List[Tuple[Optional[Node], Optional[Node]]], [])
for gd in g.objects(a, UT.graphData):
graphdata.append(
(g.value(gd, UT.graph), g.value(gd, RDFS.label))
)

r = g.value(e, MF.result)
resdata: Identifier = g.value(r, UT.data)
resgraphdata: List[Tuple[Identifier, Identifier]] = []
resdata: Optional[Node] = g.value(r, UT.data)
resgraphdata: List[Tuple[Optional[Node], Optional[Node]]] = []
for gd in g.objects(r, UT.graphData):
resgraphdata.append(
(g.value(gd, UT.graph), g.value(gd, RDFS.label))
@@ -149,7 +155,7 @@ def _str(x):
RDFT.TestTrixEval,
):
query = g.value(e, MF.action)
res = g.value(e, MF.result)
res = cast(Identifier, g.value(e, MF.result))
syntax = _type in (
RDFT.TestTurtleEval,
RDFT.TestTrigEval,
@@ -162,6 +168,8 @@ def _str(x):
print("I dont know DAWG Test Type %s" % _type)
continue

assert isinstance(e, URIRef)

yield e, _type, RDFTest(
e,
_str(name),
33 changes: 27 additions & 6 deletions test/utils/sparql_checker.py
Original file line number Diff line number Diff line change
@@ -12,7 +12,18 @@
from test.utils.iri import URIMapper
from test.utils.namespace import MF, QT, UT
from test.utils.result import ResultType, assert_bindings_collections_equal
from typing import Any, Callable, Dict, Generator, Optional, Set, Tuple, Type, Union
from typing import (
Any,
Callable,
Dict,
Generator,
Optional,
Set,
Tuple,
Type,
Union,
cast,
)
from urllib.parse import urljoin

import pytest
@@ -147,12 +158,19 @@ def __post_init__(self) -> None:

if self.type_info.query_type is not None:
assert self.result is not None
self.query = self.graph.value(self.action, self.type_info.query_property)
self.query = cast(
Optional[IdentifiedNode],
self.graph.value(self.action, self.type_info.query_property),
)
assert isinstance(self.query, URIRef)
assert self.type_info.ns is not None
self.action_data = self.graph.value(self.action, self.type_info.ns.data)
self.expected_outcome = self.graph.value(
self.action, self.type_info.expected_outcome_property
self.action_data = cast(
Optional[IdentifiedNode],
self.graph.value(self.action, self.type_info.ns.data),
)
self.expected_outcome = cast(
Optional[URIRef],
self.graph.value(self.action, self.type_info.expected_outcome_property),
)
for action_graph_data_id in self.graph.objects(
self.action, self.type_info.ns.graphData
@@ -163,7 +181,10 @@ def __post_init__(self) -> None:
self.action_graph_data = set()
self.action_graph_data.add(graph_data)
if isinstance(self.result, BNode):
self.result_data = self.graph.value(self.result, self.type_info.ns.data)
self.result_data = cast(
Optional[IdentifiedNode],
self.graph.value(self.result, self.type_info.ns.data),
)
else:
self.result_data = self.result
assert isinstance(self.result_data, URIRef)