summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Car <nicholas.car@surroundaustralia.com>2021-06-18 19:40:42 +1000
committerNicholas Car <nicholas.car@surroundaustralia.com>2021-06-18 19:40:42 +1000
commit2bc50a48c1cb4f1d932ea3541dbd1aab6ee5b91b (patch)
treee1e2fbc41b93e986088215e955465b33b95d06ef
parent88a353649c92da6903ea53b7396d9c1365c1b1ee (diff)
parent8048f37e0ae7951c0dea07c8e0ac84b801a7044c (diff)
downloadrdflib-2bc50a48c1cb4f1d932ea3541dbd1aab6ee5b91b.tar.gz
Merge branch 'master' into docco_clean
-rw-r--r--docs/intro_to_sparql.rst37
-rw-r--r--docs/sphinx-requirements.txt2
-rw-r--r--rdflib/term.py4
-rw-r--r--test/test_issue977.py30
-rw-r--r--test/test_literal.py76
-rw-r--r--test/test_parser.py42
-rw-r--r--test/test_sparqlstore.py163
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()