diff --git a/rdflib/graph.py b/rdflib/graph.py index 32c135838..8e9acc5b3 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -7,6 +7,7 @@ from io import BytesIO from typing import ( IO, + TYPE_CHECKING, Any, BinaryIO, Generator, @@ -38,6 +39,9 @@ assert Literal # avoid warning assert Namespace # avoid warning +if TYPE_CHECKING: + from rdflib.namespace import _NamespaceSetString + logger = logging.getLogger(__name__) __doc__ = """\ @@ -329,7 +333,7 @@ def __init__( identifier: Optional[Union[IdentifiedNode, str]] = None, namespace_manager: Optional[NamespaceManager] = None, base: Optional[str] = None, - bind_namespaces: str = "core", + bind_namespaces: "_NamespaceSetString" = "core", ): super(Graph, self).__init__() self.base = base @@ -344,7 +348,7 @@ def __init__( else: self.__store = store self.__namespace_manager = namespace_manager - self.bind_namespaces = bind_namespaces + self._bind_namespaces = bind_namespaces self.context_aware = False self.formula_aware = False self.default_union = False @@ -363,7 +367,7 @@ def namespace_manager(self): this graph's namespace-manager """ if self.__namespace_manager is None: - self.__namespace_manager = NamespaceManager(self, self.bind_namespaces) + self.__namespace_manager = NamespaceManager(self, self._bind_namespaces) return self.__namespace_manager @namespace_manager.setter diff --git a/rdflib/namespace/__init__.py b/rdflib/namespace/__init__.py index 428e4d5a4..7f77f02ec 100644 --- a/rdflib/namespace/__init__.py +++ b/rdflib/namespace/__init__.py @@ -1,5 +1,6 @@ import json import logging +import sys import warnings from functools import lru_cache from pathlib import Path @@ -342,6 +343,14 @@ def _ipython_key_completions_(self) -> List[str]: XMLNS = Namespace("http://www.w3.org/XML/1998/namespace") +if TYPE_CHECKING: + if sys.version_info >= (3, 8): + from typing import Literal as PyLiteral + else: + from typing_extensions import Literal as PyLiteral + + _NamespaceSetString = PyLiteral["core", "rdflib", "none"] + class NamespaceManager(object): """Class for managing prefix => namespace mappings @@ -386,7 +395,7 @@ class NamespaceManager(object): """ - def __init__(self, graph: "Graph", bind_namespaces: str = "core"): + def __init__(self, graph: "Graph", bind_namespaces: "_NamespaceSetString" = "core"): self.graph = graph self.__cache: Dict[str, Tuple[str, URIRef, str]] = {} self.__cache_strict: Dict[str, Tuple[str, URIRef, str]] = {} @@ -405,10 +414,10 @@ def __init__(self, graph: "Graph", bind_namespaces: str = "core"): pass elif bind_namespaces == "rdflib": # bind all the Namespaces shipped with RDFLib - for prefix, ns in NAMESPACE_PREFIXES_RDFLIB.items(): + for prefix, ns in _NAMESPACE_PREFIXES_RDFLIB.items(): self.bind(prefix, ns) # ... don't forget the core ones too - for prefix, ns in NAMESPACE_PREFIXES_CORE.items(): + for prefix, ns in _NAMESPACE_PREFIXES_CORE.items(): self.bind(prefix, ns) elif bind_namespaces == "cc": # bind any prefix that can be found with lookups to prefix.cc @@ -416,10 +425,12 @@ def __init__(self, graph: "Graph", bind_namespaces: str = "core"): # work out remainder - namespaces without prefixes # only look those ones up raise NotImplementedError("Haven't got to this option yet") - else: # bind_namespaces == "core": + elif bind_namespaces == "core": # bind a few core RDF namespaces - default - for prefix, ns in NAMESPACE_PREFIXES_CORE.items(): + for prefix, ns in _NAMESPACE_PREFIXES_CORE.items(): self.bind(prefix, ns) + else: + raise ValueError(f"unsupported namespace set {bind_namespaces}") def __contains__(self, ref: str) -> bool: # checks if a reference is in any of the managed namespaces with syntax @@ -828,7 +839,7 @@ def get_longest_namespace(trie: Dict[str, Any], value: str) -> Optional[str]: from rdflib.namespace._XSD import XSD # prefixes for the core Namespaces shipped with RDFLib -NAMESPACE_PREFIXES_CORE = { +_NAMESPACE_PREFIXES_CORE = { "owl": OWL, "rdf": RDF, "rdfs": RDFS, @@ -839,7 +850,7 @@ def get_longest_namespace(trie: Dict[str, Any], value: str) -> Optional[str]: # prefixes for all the non-core Namespaces shipped with RDFLib -NAMESPACE_PREFIXES_RDFLIB = { +_NAMESPACE_PREFIXES_RDFLIB = { "brick": BRICK, "csvw": CSVW, "dc": DC, diff --git a/test/test_namespacemanager.py b/test/test_namespacemanager.py index d5dc78e33..1206f931f 100644 --- a/test/test_namespacemanager.py +++ b/test/test_namespacemanager.py @@ -1,15 +1,22 @@ +import logging import sys +from contextlib import ExitStack from pathlib import Path +from typing import Any, Dict, Optional, Set, Tuple, Type, Union +import pytest + +from rdflib.graph import Dataset from rdflib.term import URIRef sys.path.append(str(Path(__file__).parent.parent.absolute())) from rdflib import Graph from rdflib.namespace import ( - NAMESPACE_PREFIXES_CORE, - NAMESPACE_PREFIXES_RDFLIB, + _NAMESPACE_PREFIXES_CORE, + _NAMESPACE_PREFIXES_RDFLIB, OWL, RDFS, + NamespaceManager, ) @@ -18,7 +25,7 @@ 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_CORE) pre = sorted([x[0] for x in list(g.namespaces())]) assert pre == ["owl", "rdf", "rdfs", "xml", "xsd"] @@ -27,8 +34,8 @@ def test_rdflib_prefixes_bound(): g = Graph(bind_namespaces="rdflib") # the core 5 + the extra 23 namespaces with prefixes - assert len(list(g.namespaces())) == len(NAMESPACE_PREFIXES_CORE) + len( - list(NAMESPACE_PREFIXES_RDFLIB) + assert len(list(g.namespaces())) == len(_NAMESPACE_PREFIXES_CORE) + len( + list(_NAMESPACE_PREFIXES_RDFLIB) ) @@ -78,3 +85,80 @@ def test_replace(): assert ("rdfs", URIRef("http://example.com")) in list( g.namespace_manager.namespaces() ) + + +def test_invalid_selector() -> None: + graph = Graph() + with pytest.raises(ValueError): + NamespaceManager(graph, bind_namespaces="invalid") # type: ignore[arg-type] + + +NamespaceSet = Set[Tuple[str, URIRef]] + + +def check_graph_ns( + graph: Graph, + expected_nsmap: Dict[str, Any], + check_namespaces: Optional[NamespaceSet] = None, +) -> None: + expected_namespaces = { + (prefix, URIRef(f"{uri}")) for prefix, uri in expected_nsmap.items() + } + logging.debug("expected_namespaces = %s", expected_namespaces) + graph_namespaces = {*graph.namespaces()} + assert expected_namespaces == graph_namespaces + nman_namespaces = {*graph.namespace_manager.namespaces()} + assert expected_namespaces == nman_namespaces + if check_namespaces is not None: + assert expected_namespaces == check_namespaces + logging.debug("check_namespaces = %s", check_namespaces) + + +@pytest.mark.parametrize( + ["selector", "expected_result"], + [ + (None, ValueError), + ("invalid", ValueError), + ("core", _NAMESPACE_PREFIXES_CORE), + ("rdflib", {**_NAMESPACE_PREFIXES_CORE, **_NAMESPACE_PREFIXES_RDFLIB}), + ("none", {}), + ], +) +def test_graph_bind_namespaces( + selector: Any, + expected_result: Union[Dict[str, Any], Type[Exception]], +) -> None: + namespaces: Optional[NamespaceSet] = None + with ExitStack() as xstack: + if not isinstance(expected_result, dict): + xstack.enter_context(pytest.raises(expected_result)) + graph = Graph(bind_namespaces=selector) + namespaces = {*graph.namespaces()} + if isinstance(expected_result, dict): + assert namespaces is not None + check_graph_ns(graph, expected_result, namespaces) + else: + assert namespaces is None + + +@pytest.mark.parametrize( + ["selector", "expected_result"], + [ + (None, ValueError), + ("invalid", ValueError), + ("core", _NAMESPACE_PREFIXES_CORE), + ("rdflib", {**_NAMESPACE_PREFIXES_CORE, **_NAMESPACE_PREFIXES_RDFLIB}), + ("none", {}), + ], +) +def test_nman_bind_namespaces( + selector: Any, + expected_result: Union[Dict[str, Any], Type[Exception]], +) -> None: + with ExitStack() as xstack: + if not isinstance(expected_result, dict): + xstack.enter_context(pytest.raises(expected_result)) + graph = Dataset() + graph.namespace_manager = NamespaceManager(graph, selector) + if isinstance(expected_result, dict): + check_graph_ns(graph, expected_result)