From 57bb42886b57a37f1ba93a4d1b52651d978d049c Mon Sep 17 00:00:00 2001 From: Iwan Aucamp Date: Sun, 26 Mar 2023 12:53:29 +0200 Subject: fix: restore the 6.1.1 default bound namespaces (#2313) The namespaces bound by default by `rdflib.graph.Graph` and `rdflib.namespace.NamespaceManager` was reduced in version 6.2.0 of RDFLib, however, this also would cause code that worked with 6.1.1 to break, so this constituted a breaking change. This change restores the previous behaviour, binding the same namespaces as was bound in 6.1.1. To bind a reduced set of namespaces, the `bind_namespaces` parameter of `rdflib.graph.Graph` or `rdflib.namespace.NamespaceManager` can be used. - Closes . --- rdflib/graph.py | 2 +- rdflib/namespace/__init__.py | 15 ++++- test/test_graph/test_namespace_rebinding.py | 3 +- test/test_namespace/test_namespacemanager.py | 96 +++++++++++++++++++++++++-- test/test_serializers/test_xmlwriter_qname.py | 4 +- test/test_sparql/test_service.py | 16 +++-- test/utils/httpservermock.py | 5 +- 7 files changed, 121 insertions(+), 20 deletions(-) diff --git a/rdflib/graph.py b/rdflib/graph.py index 7d32ab38..4a96e6d3 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -437,7 +437,7 @@ class Graph(Node): identifier: Optional[Union[_ContextIdentifierType, str]] = None, namespace_manager: Optional[NamespaceManager] = None, base: Optional[str] = None, - bind_namespaces: "_NamespaceSetString" = "core", + bind_namespaces: "_NamespaceSetString" = "rdflib", ): super(Graph, self).__init__() self.base = base diff --git a/rdflib/namespace/__init__.py b/rdflib/namespace/__init__.py index fb6d845b..c88fdedd 100644 --- a/rdflib/namespace/__init__.py +++ b/rdflib/namespace/__init__.py @@ -360,13 +360,13 @@ class NamespaceManager(object): * core: * binds several core RDF prefixes only * owl, rdf, rdfs, xsd, xml from the NAMESPACE_PREFIXES_CORE object - * this is default * rdflib: * binds all the namespaces shipped with RDFLib as DefinedNamespace instances * all the core namespaces and all the following: brick, csvw, dc, dcat * dcmitype, dcterms, dcam, doap, foaf, geo, odrl, org, prof, prov, qb, schema * sh, skos, sosa, ssn, time, vann, void * see the NAMESPACE_PREFIXES_RDFLIB object for the up-to-date list + * this is default * none: * binds no namespaces to prefixes * note this is NOT default behaviour @@ -374,6 +374,14 @@ class NamespaceManager(object): * using prefix bindings from prefix.cc which is a online prefixes database * not implemented yet - this is aspirational + .. attention:: + + The namespaces bound for specific values of ``bind_namespaces`` + constitute part of RDFLib's public interface, so changes to them should + only be additive within the same minor version. Removing values, or + removing namespaces that are bound by default, constitutes a breaking + change. + See the Sample usage @@ -390,10 +398,11 @@ class NamespaceManager(object): >>> all_ns = [n for n in g.namespace_manager.namespaces()] >>> assert ('ex', rdflib.term.URIRef('http://example.com/')) in all_ns >>> - """ - def __init__(self, graph: "Graph", bind_namespaces: "_NamespaceSetString" = "core"): + def __init__( + self, graph: "Graph", bind_namespaces: "_NamespaceSetString" = "rdflib" + ): self.graph = graph self.__cache: Dict[str, Tuple[str, URIRef, str]] = {} self.__cache_strict: Dict[str, Tuple[str, URIRef, str]] = {} diff --git a/test/test_graph/test_namespace_rebinding.py b/test/test_graph/test_namespace_rebinding.py index 3125d57e..15cf4473 100644 --- a/test/test_graph/test_namespace_rebinding.py +++ b/test/test_graph/test_namespace_rebinding.py @@ -3,7 +3,7 @@ from test.data import context1, context2, tarek import pytest from rdflib import ConjunctiveGraph, Graph, Literal -from rdflib.namespace import OWL, Namespace +from rdflib.namespace import OWL, Namespace, NamespaceManager from rdflib.plugins.stores.memory import Memory from rdflib.term import URIRef @@ -294,6 +294,7 @@ def test_multigraph_bindings(): # Including newly-created objects that use the store cg = ConjunctiveGraph(store=store) + cg.namespace_manager = NamespaceManager(cg, bind_namespaces="core") assert ("foaf", foaf1_uri) not in list(cg.namespaces()) assert ("friend-of-a-friend", foaf1_uri) in list(cg.namespaces()) diff --git a/test/test_namespace/test_namespacemanager.py b/test/test_namespace/test_namespacemanager.py index d79f0419..20cb9594 100644 --- a/test/test_namespace/test_namespacemanager.py +++ b/test/test_namespace/test_namespacemanager.py @@ -33,9 +33,41 @@ def test_core_prefixes_bound(): g = Graph() # prefixes in Graph - assert len(list(g.namespaces())) == len(_NAMESPACE_PREFIXES_CORE) + assert len(list(g.namespaces())) == len( + {**_NAMESPACE_PREFIXES_RDFLIB, **_NAMESPACE_PREFIXES_CORE} + ) pre = sorted([x[0] for x in list(g.namespaces())]) - assert pre == ["owl", "rdf", "rdfs", "xml", "xsd"] + assert pre == [ + "brick", + "csvw", + "dc", + "dcam", + "dcat", + "dcmitype", + "dcterms", + "doap", + "foaf", + "geo", + "odrl", + "org", + "owl", + "prof", + "prov", + "qb", + "rdf", + "rdfs", + "schema", + "sh", + "skos", + "sosa", + "ssn", + "time", + "vann", + "void", + "wgs", + "xml", + "xsd", + ] def test_rdflib_prefixes_bound(): @@ -175,6 +207,40 @@ def test_nman_bind_namespaces( @pytest.mark.parametrize( ["selector", "expected_bindings"], [ + ( + None, + { + "brick": "https://brickschema.org/schema/Brick#", + "csvw": "http://www.w3.org/ns/csvw#", + "dc": "http://purl.org/dc/elements/1.1/", + "dcat": "http://www.w3.org/ns/dcat#", + "dcmitype": "http://purl.org/dc/dcmitype/", + "dcterms": "http://purl.org/dc/terms/", + "dcam": "http://purl.org/dc/dcam/", + "doap": "http://usefulinc.com/ns/doap#", + "foaf": "http://xmlns.com/foaf/0.1/", + "odrl": "http://www.w3.org/ns/odrl/2/", + "geo": "http://www.opengis.net/ont/geosparql#", + "org": "http://www.w3.org/ns/org#", + "owl": "http://www.w3.org/2002/07/owl#", + "prof": "http://www.w3.org/ns/dx/prof/", + "prov": "http://www.w3.org/ns/prov#", + "qb": "http://purl.org/linked-data/cube#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "schema": "https://schema.org/", + "sh": "http://www.w3.org/ns/shacl#", + "skos": "http://www.w3.org/2004/02/skos/core#", + "sosa": "http://www.w3.org/ns/sosa/", + "ssn": "http://www.w3.org/ns/ssn/", + "time": "http://www.w3.org/2006/time#", + "vann": "http://purl.org/vocab/vann/", + "void": "http://rdfs.org/ns/void#", + "wgs": "https://www.w3.org/2003/01/geo/wgs84_pos#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "xml": "http://www.w3.org/XML/1998/namespace", + }, + ), ( "rdflib", { @@ -208,19 +274,39 @@ def test_nman_bind_namespaces( "xsd": "http://www.w3.org/2001/XMLSchema#", "xml": "http://www.w3.org/XML/1998/namespace", }, - ) + ), + ( + "core", + { + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "xml": "http://www.w3.org/XML/1998/namespace", + }, + ), ], ) def test_bound_namespaces_subset( - selector: Any, expected_bindings: Dict[str, str] + selector: Optional[Any], expected_bindings: Dict[str, str] ) -> None: - graph = Graph(bind_namespaces=selector) + if selector is not None: + graph = Graph(bind_namespaces=selector) + else: + graph = Graph() bound_namespaces = dict( (key, str(value)) for key, value in graph.namespace_manager.namespaces() ) assert ( expected_bindings.items() <= bound_namespaces.items() ), f"missing items {expected_bindings.items() - bound_namespaces.items()}" + empty_graph = Graph(bind_namespaces="none") + if selector is not None: + nman = NamespaceManager(empty_graph, bind_namespaces=selector) + else: + nman = NamespaceManager(empty_graph) + nman_bound_namespaces = dict((key, str(value)) for key, value in nman.namespaces()) + assert bound_namespaces == nman_bound_namespaces def test_compute_qname_no_generate() -> None: diff --git a/test/test_serializers/test_xmlwriter_qname.py b/test/test_serializers/test_xmlwriter_qname.py index 662d3f59..13ee84a0 100644 --- a/test/test_serializers/test_xmlwriter_qname.py +++ b/test/test_serializers/test_xmlwriter_qname.py @@ -10,7 +10,7 @@ TRIXNS = rdflib.Namespace("http://www.w3.org/2004/03/trix/trix-1/") def test_xmlwriter_namespaces(): - g = rdflib.Graph() + g = rdflib.Graph(bind_namespaces="core") with tempfile.TemporaryFile() as fp: xmlwr = XMLWriter(fp, g.namespace_manager, extra_ns={"": TRIXNS, "ex": EXNS}) @@ -32,7 +32,7 @@ def test_xmlwriter_namespaces(): def test_xmlwriter_decl(): - g = rdflib.Graph() + g = rdflib.Graph(bind_namespaces="core") with tempfile.TemporaryFile() as fp: xmlwr = XMLWriter(fp, g.namespace_manager, decl=0, extra_ns={"": TRIXNS}) diff --git a/test/test_sparql/test_service.py b/test/test_sparql/test_service.py index d83ac32e..3a827054 100644 --- a/test/test_sparql/test_service.py +++ b/test/test_sparql/test_service.py @@ -330,14 +330,16 @@ def test_with_mock( "head": {"vars": ["var"]}, "results": {"bindings": [{"var": item} for item in response_bindings]}, } - function_httpmock.responses[MethodName.GET].append( - MockHTTPResponse( - 200, - "OK", - json.dumps(response).encode("utf-8"), - {"Content-Type": ["application/sparql-results+json"]}, - ) + mock_response = MockHTTPResponse( + 200, + "OK", + json.dumps(response).encode("utf-8"), + {"Content-Type": ["application/sparql-results+json"]}, ) + # Adding the same response for GET and POST as the method used by RDFLib is + # dependent on the size of the service query. + function_httpmock.responses[MethodName.GET].append(mock_response) + function_httpmock.responses[MethodName.POST].append(mock_response) catcher: Optional[pytest.ExceptionInfo[Exception]] = None with ExitStack() as xstack: diff --git a/test/utils/httpservermock.py b/test/utils/httpservermock.py index 54596feb..6a87bf19 100644 --- a/test/utils/httpservermock.py +++ b/test/utils/httpservermock.py @@ -96,7 +96,10 @@ class BaseHTTPServerMock: logging.debug("headers %s", request.headers) requests[method_name].append(request) - response = responses[method_name].pop(0) + try: + response = responses[method_name].pop(0) + except IndexError as error: + raise ValueError(f"No response for {method_name} request") from error handler.send_response(response.status_code, response.reason_phrase) apply_headers_to(response.headers, handler) handler.end_headers() -- cgit v1.2.1