diff options
author | Nicholas Car <nicholas.car@surroundaustralia.com> | 2021-06-18 19:40:42 +1000 |
---|---|---|
committer | Nicholas Car <nicholas.car@surroundaustralia.com> | 2021-06-18 19:40:42 +1000 |
commit | 2bc50a48c1cb4f1d932ea3541dbd1aab6ee5b91b (patch) | |
tree | e1e2fbc41b93e986088215e955465b33b95d06ef | |
parent | 88a353649c92da6903ea53b7396d9c1365c1b1ee (diff) | |
parent | 8048f37e0ae7951c0dea07c8e0ac84b801a7044c (diff) | |
download | rdflib-2bc50a48c1cb4f1d932ea3541dbd1aab6ee5b91b.tar.gz |
Merge branch 'master' into docco_clean
-rw-r--r-- | docs/intro_to_sparql.rst | 37 | ||||
-rw-r--r-- | docs/sphinx-requirements.txt | 2 | ||||
-rw-r--r-- | rdflib/term.py | 4 | ||||
-rw-r--r-- | test/test_issue977.py | 30 | ||||
-rw-r--r-- | test/test_literal.py | 76 | ||||
-rw-r--r-- | test/test_parser.py | 42 | ||||
-rw-r--r-- | test/test_sparqlstore.py | 163 |
7 files changed, 329 insertions, 25 deletions
diff --git a/docs/intro_to_sparql.rst b/docs/intro_to_sparql.rst index 3ad00c2c..e80cae58 100644 --- a/docs/intro_to_sparql.rst +++ b/docs/intro_to_sparql.rst @@ -29,19 +29,16 @@ For example... .. code-block:: python import rdflib - g = rdflib.Graph() + g.parse("http://danbri.org/foaf.rdf#") - # ... add some triples to g somehow ... - g.parse("some_foaf_file.rdf") - - qres = g.query( - """SELECT DISTINCT ?aname ?bname - WHERE { - ?a foaf:knows ?b . - ?a foaf:name ?aname . - ?b foaf:name ?bname . - }""") + knows_query = """ + SELECT DISTINCT ?aname ?bname + WHERE { + ?a foaf:knows ?b . + ?a foaf:name ?aname . + ?b foaf:name ?bname . + }""" for row in qres: print(f"{row.aname} knows {row.bname}") @@ -175,18 +172,18 @@ initial bindings: .. code-block:: python - q = prepareQuery( - "SELECT ?s WHERE { ?person foaf:knows ?s .}", - initNs = { "foaf": FOAF } - ) + q = prepareQuery( + "SELECT ?s WHERE { ?person foaf:knows ?s .}", + initNs = { "foaf": FOAF } + ) - g = rdflib.Graph() - g.load("foaf.rdf") + g = rdflib.Graph() + g.load("foaf.rdf") - tim = rdflib.URIRef("http://www.w3.org/People/Berners-Lee/card#i") + tim = rdflib.URIRef("http://www.w3.org/People/Berners-Lee/card#i") - for row in g.query(q, initBindings={'person': tim}): - print(row) + for row in g.query(q, initBindings={'person': tim}): + print(row) Custom Evaluation Functions diff --git a/docs/sphinx-requirements.txt b/docs/sphinx-requirements.txt index 48ba7549..b4f0ddd9 100644 --- a/docs/sphinx-requirements.txt +++ b/docs/sphinx-requirements.txt @@ -1,3 +1,3 @@ -sphinx==4.0.1 +sphinx==4.0.2 sphinxcontrib-apidoc git+https://github.com/gniezen/n3pygments.git diff --git a/rdflib/term.py b/rdflib/term.py index 0f627e69..d9e06d82 100644 --- a/rdflib/term.py +++ b/rdflib/term.py @@ -1437,6 +1437,8 @@ _XSD_YEARMONTHDURATION = URIRef(_XSD_PFX + "yearMonthDuration") _OWL_RATIONAL = URIRef("http://www.w3.org/2002/07/owl#rational") _XSD_B64BINARY = URIRef(_XSD_PFX + "base64Binary") _XSD_HEXBINARY = URIRef(_XSD_PFX + "hexBinary") +_XSD_GYEAR = URIRef(_XSD_PFX + "gYear") +_XSD_GYEARMONTH = URIRef(_XSD_PFX + "gYearMonth") # TODO: gYearMonth, gYear, gMonthDay, gDay, gMonth _NUMERIC_LITERAL_TYPES = ( @@ -1558,6 +1560,8 @@ _GenericPythonToXSDRules = [ ] _SpecificPythonToXSDRules = [ + ((date, _XSD_GYEAR), lambda val: val.strftime("%04Y")), + ((date, _XSD_GYEARMONTH), lambda val: val.strftime("%04Y-%02m")), ((str, _XSD_HEXBINARY), hexlify), ((bytes, _XSD_HEXBINARY), hexlify), ((str, _XSD_B64BINARY), b64encode), diff --git a/test/test_issue977.py b/test/test_issue977.py new file mode 100644 index 00000000..26f61c5a --- /dev/null +++ b/test/test_issue977.py @@ -0,0 +1,30 @@ +import unittest + +from rdflib import Graph, XSD, RDF, RDFS, URIRef, Literal + + +class TestIssue977(unittest.TestCase): + + def setUp(self): + self.graph = Graph() + # Bind prefixes. + self.graph.bind('isbn', 'urn:isbn:') + self.graph.bind('webn', 'http://w3c.org/example/isbn/') + # Populate graph. + self.graph.add((URIRef('urn:isbn:1503280780'), RDFS.label, Literal('Moby Dick'))) + self.graph.add((URIRef('http://w3c.org/example/isbn/1503280780'), RDFS.label, Literal('Moby Dick'))) + + def test_namespace_manager(self): + assert 'isbn', 'urn:isbn:' in tuple(self.graph.namespaces()) + assert 'webn', 'http://w3c.org/example/isbn/' in tuple(self.graph.namespaces()) + + def test_turtle_serialization(self): + serialization = self.graph.serialize(None, format='turtle') + print(f'Test Issue 977, serialization output:\n---\n{serialization}---') + # Test serialization. + assert '@prefix webn:' in serialization, "Prefix webn not found in serialization!" + assert '@prefix isbn:' in serialization, "Prefix isbn not found in serialization!" + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_literal.py b/test/test_literal.py index 656bfb10..98cd18a8 100644 --- a/test/test_literal.py +++ b/test/test_literal.py @@ -1,7 +1,9 @@ import unittest +import datetime import rdflib # needed for eval(repr(...)) below from rdflib.term import Literal, URIRef, _XSD_DOUBLE, bind, _XSD_BOOLEAN +from rdflib.namespace import XSD def uformat(s): @@ -188,5 +190,79 @@ class TestBindings(unittest.TestCase): self.assertEqual(specific_l.datatype, datatype) +class TestXsdLiterals(unittest.TestCase): + def test_make_literals(self): + """ + Tests literal construction. + """ + inputs = [ + # these literals do not get conerted to python types + ("ABCD", XSD.integer, None), + ("ABCD", XSD.gYear, None), + ("-10000", XSD.gYear, None), + ("-1921-00", XSD.gYearMonth, None), + ("1921-00", XSD.gMonthDay, None), + ("1921-13", XSD.gMonthDay, None), + ("-1921-00", XSD.gMonthDay, None), + ("10", XSD.gDay, None), + ("-1", XSD.gDay, None), + ("0000", XSD.gYear, None), + ("0000-00-00", XSD.date, None), + ("NOT A VALID HEX STRING", XSD.hexBinary, None), + ("NOT A VALID BASE64 STRING", XSD.base64Binary, None), + # these literals get converted to python types + ("1921-05-01", XSD.date, datetime.date), + ("1921-05-01T00:00:00", XSD.dateTime, datetime.datetime), + ("1921-05", XSD.gYearMonth, datetime.date), + ("0001-01", XSD.gYearMonth, datetime.date), + ("0001-12", XSD.gYearMonth, datetime.date), + ("2002-01", XSD.gYearMonth, datetime.date), + ("9999-01", XSD.gYearMonth, datetime.date), + ("9999-12", XSD.gYearMonth, datetime.date), + ("1921", XSD.gYear, datetime.date), + ("2000", XSD.gYear, datetime.date), + ("0001", XSD.gYear, datetime.date), + ("9999", XSD.gYear, datetime.date), + ("1982", XSD.gYear, datetime.date), + ("2002", XSD.gYear, datetime.date), + ("1921-05-01T00:00:00+00:30", XSD.dateTime, datetime.datetime), + ("1921-05-01T00:00:00-00:30", XSD.dateTime, datetime.datetime), + ("abcdef0123", XSD.hexBinary, bytes), + ("", XSD.hexBinary, bytes), + ("UkRGTGli", XSD.base64Binary, bytes), + ("", XSD.base64Binary, bytes), + ] + self.check_make_literals(inputs) + + @unittest.expectedFailure + def test_make_literals_ki(self): + """ + Known issues with literal construction. + """ + inputs = [ + ("1921-01Z", XSD.gYearMonth, datetime.date), + ("1921Z", XSD.gYear, datetime.date), + ("1921-00", XSD.gYearMonth, datetime.date), + ("1921-05-01Z", XSD.date, datetime.date), + ("1921-05-01+00:30", XSD.date, datetime.date), + ("1921-05-01+00:30", XSD.date, datetime.date), + ("1921-05-01+00:00", XSD.date, datetime.date), + ("1921-05-01+00:00", XSD.date, datetime.date), + ("1921-05-01T00:00:00Z", XSD.dateTime, datetime.datetime), + ] + self.check_make_literals(inputs) + + def check_make_literals(self, inputs): + for literal_pair in inputs: + (lexical, type, value_cls) = literal_pair + with self.subTest(f"tesing {literal_pair}"): + literal = Literal(lexical, datatype=type) + if value_cls is not None: + self.assertIsInstance(literal.value, value_cls) + else: + self.assertIsNone(literal.value) + self.assertEqual(lexical, f"{literal}") + + if __name__ == "__main__": unittest.main() diff --git a/test/test_parser.py b/test/test_parser.py index e337969c..da38ea91 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -44,5 +44,47 @@ class ParserTestCase(unittest.TestCase): self.assertEqual(type, RDFS.Class) +class TestGitHubIssues(unittest.TestCase): + def test_issue_1228_a(self): + data = """ + PREFIX sdo: <https://schema.org/> + PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + <x:> sdo:startDate "1982"^^xsd:gYear . + """ + + g = Graph().parse(data=data, format="ttl") + self.assertNotIn("1982-01-01", data) + self.assertNotIn("1982-01-01", g.serialize(format="ttl")) + + def test_issue_1228_b(self): + data = """\ +<?xml version="1.0" encoding="UTF-8"?> + <rdf:RDF + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:sdo="https://schema.org/" + > + <rdf:Description rdf:about="x:"> + <sdo:startDate + rdf:datatype="http://www.w3.org/2001/XMLSchema#gYear">1982</sdo:startDate> + </rdf:Description> +</rdf:RDF>""" + + g = Graph().parse(data=data, format="xml") + self.assertNotIn("1982-01-01", data) + self.assertNotIn("1982-01-01", g.serialize(format="xml")) + + def test_issue_806(self): + data = ( + "<http://dbpedia.org/resource/Australian_Labor_Party> " + "<http://dbpedia.org/ontology/formationYear> " + '"1891"^^<http://www.w3.org/2001/XMLSchema#gYear> .' + ) + g = Graph() + g.parse(data=data, format="nt") + for _, _, o in g: + self.assertNotIn("1891-01-01", o.n3()) + + if __name__ == "__main__": unittest.main() diff --git a/test/test_sparqlstore.py b/test/test_sparqlstore.py index 8d720de3..8b11a4c4 100644 --- a/test/test_sparqlstore.py +++ b/test/test_sparqlstore.py @@ -3,9 +3,18 @@ from urllib.request import urlopen from urllib.error import HTTPError import unittest from nose import SkipTest -from http.server import BaseHTTPRequestHandler, HTTPServer +from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler import socket from threading import Thread +from contextlib import contextmanager +from unittest.mock import MagicMock, Mock, patch +import typing as t +import random +import collections +from urllib.parse import ParseResult, urlparse, parse_qs +from rdflib.namespace import RDF, XSD, XMLNS, FOAF, RDFS +from rdflib.plugins.stores.sparqlstore import SPARQLConnector +import email.message from . import helper @@ -33,9 +42,26 @@ class SPARQLStoreDBPediaTestCase(unittest.TestCase): def test_Query(self): query = "select distinct ?Concept where {[] a ?Concept} LIMIT 1" - res = helper.query_with_retry(self.graph, query, initNs={}) - for i in res: - assert type(i[0]) == URIRef, i[0].n3() + _query = SPARQLConnector.query + with patch("rdflib.plugins.stores.sparqlstore.SPARQLConnector.query") as mock: + SPARQLConnector.query.side_effect = lambda *args, **kwargs: _query( + self.graph.store, *args, **kwargs + ) + res = helper.query_with_retry(self.graph, query, initNs={}) + count = 0 + for i in res: + count += 1 + assert type(i[0]) == URIRef, i[0].n3() + assert count > 0 + mock.assert_called_once() + args, kwargs = mock.call_args + + def unpacker(query, default_graph=None, named_graph=None): + return query, default_graph, named_graph + + (mquery, _, _) = unpacker(*args, *kwargs) + for _, uri in self.graph.namespaces(): + assert mquery.count(f"<{uri}>") == 1 def test_initNs(self): query = """\ @@ -196,5 +222,134 @@ class SPARQL11ProtocolStoreMock(BaseHTTPRequestHandler): return +def get_random_ip(parts: t.List[str] = None) -> str: + if parts is None: + parts = ["127"] + for index in range(4 - len(parts)): + parts.append(f"{random.randint(0, 255)}") + return ".".join(parts) + + +@contextmanager +def ctx_http_server(handler: t.Type[BaseHTTPRequestHandler]) -> t.Iterator[HTTPServer]: + host = get_random_ip() + server = HTTPServer((host, 0), handler) + server_thread = Thread(target=server.serve_forever) + server_thread.daemon = True + server_thread.start() + yield server + server.shutdown() + server.socket.close() + server_thread.join() + + +GenericT = t.TypeVar("GenericT", bound=t.Any) + + +def make_spypair(method: GenericT) -> t.Tuple[GenericT, Mock]: + m = MagicMock() + + def wrapper(self: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Any: + m(*args, **kwargs) + return method(self, *args, **kwargs) + + setattr(wrapper, "mock", m) + return t.cast(GenericT, wrapper), m + + +HeadersT = t.Dict[str, t.List[str]] +PathQueryT = t.Dict[str, t.List[str]] + + +class MockHTTPRequests(t.NamedTuple): + path: str + parsed_path: ParseResult + path_query: PathQueryT + headers: email.message.Message + + +class MockHTTPResponse(t.NamedTuple): + status_code: int + reason_phrase: str + body: bytes + headers: HeadersT = collections.defaultdict(list) + + +class SPARQLMockTests(unittest.TestCase): + requests: t.List[MockHTTPRequests] = [] + responses: t.List[MockHTTPResponse] = [] + + def setUp(self): + _tc = self + + class Handler(SimpleHTTPRequestHandler): + tc = _tc + + def _do_GET(self): + parsed_path = urlparse(self.path) + path_query = parse_qs(parsed_path.query) + request = MockHTTPRequests( + self.path, parsed_path, path_query, self.headers + ) + self.tc.requests.append(request) + + response = self.tc.responses.pop(0) + self.send_response(response.status_code, response.reason_phrase) + for header, values in response.headers.items(): + for value in values: + self.send_header(header, value) + self.end_headers() + + self.wfile.write(response.body) + self.wfile.flush() + return + + (do_GET, do_GET_mock) = make_spypair(_do_GET) + self.Handler = Handler + self.requests.clear() + self.responses.clear() + + def test_query(self): + triples = { + (RDFS.Resource, RDF.type, RDFS.Class), + (RDFS.Resource, RDFS.isDefinedBy, URIRef(RDFS)), + (RDFS.Resource, RDFS.label, Literal("Resource")), + (RDFS.Resource, RDFS.comment, Literal("The class resource, everything.")), + } + rows = "\n".join([f'"{s}","{p}","{o}"' for s, p, o in triples]) + response_body = f"s,p,o\n{rows}".encode() + response = MockHTTPResponse(200, "OK", response_body) + response.headers["Content-Type"].append("text/csv; charset=utf-8") + self.responses.append(response) + + graph = Graph(store="SPARQLStore", identifier="http://example.com") + graph.bind("xsd", XSD) + graph.bind("xml", XMLNS) + graph.bind("foaf", FOAF) + graph.bind("rdf", RDF) + + assert len(list(graph.namespaces())) >= 4 + + with ctx_http_server(self.Handler) as server: + (host, port) = server.server_address + url = f"http://{host}:{port}/query" + graph.open(url) + query_result = graph.query("SELECT ?s ?p ?o WHERE { ?s ?p ?o }") + + rows = set(query_result) + assert len(rows) == len(triples) + for triple in triples: + assert triple in rows + + self.Handler.do_GET_mock.assert_called_once() + assert len(self.requests) == 1 + request = self.requests.pop() + assert len(request.path_query["query"]) == 1 + query = request.path_query["query"][0] + + for _, uri in graph.namespaces(): + assert query.count(f"<{uri}>") == 1 + + if __name__ == "__main__": unittest.main() |