diff --git a/rdflib/namespace/__init__.py b/rdflib/namespace/__init__.py index 3e8753393..f8e69bfaa 100644 --- a/rdflib/namespace/__init__.py +++ b/rdflib/namespace/__init__.py @@ -491,10 +491,27 @@ def qname(self, uri: str) -> str: return ":".join((prefix, name)) def curie(self, uri: str) -> str: - """From a URI, generate a valid CURIE. + """ + From a URI, generate a valid CURIE. + + Result is guaranteed to contain a colon separating the prefix from the + name, even if the prefix is an empty string. + + .. warning:: + + If there is no matching namespace for the URI in the namespace + manager then a new namespace will be added with prefix + ``ns{index}``. + + Because of this side effect this is not a pure function. + + This is the same behaviour as `NamespaceManager.qname`. + + + + :param uri: URI to generate CURIE for. + :return: CURIE for the URI. - Result is guaranteed to contain a colon separating the prefix from the name, - even if the prefix is an empty string. """ prefix, namespace, name = self.compute_qname(uri) return ":".join((prefix, name)) diff --git a/test/data/owl/test_ontology.owl b/test/data/owl/test_ontology.owl deleted file mode 100644 index 51ff107ac..000000000 --- a/test/data/owl/test_ontology.owl +++ /dev/null @@ -1,315 +0,0 @@ - - - - Orson B Osborn - Test OBO ontology - 0.1.0 - - - - - - - - - - - - - editor preferred term - - The concise, meaningful, and human-friendly name for a class or property preferred by the ontology developers. (US-English) - PERSON:Daniel Schober - GROUP:OBI:<http://purl.obolibrary.org/obo/obi> - - editor preferred term - - - - - - - - example - example of usage - - A phrase describing how a class name should be used. May also include other kinds of examples that facilitate immediate understanding of a class semantics, such as widely known prototypical subclasses or instances of the class. Although essential for high level terms, examples for low level terms (e.g., Affymetrix HU133 array) are not - A phrase describing how a term should be used and/or a citation to a work which uses it. May also include other kinds of examples that facilitate immediate understanding, such as widely know prototypes or instances of a class, or cases where a relation is said to hold. - PERSON:Daniel Schober - GROUP:OBI:<http://purl.obolibrary.org/obo/obi> - IAO:0000112 - uberon - example_of_usage - true - example_of_usage - - example of usage - - - - - - - - has curation status - - - - - - - - definition - - - - - - - - term editor - - - - - - - - alternative term - - - - - - - - definition source - - - - - - - - is direct form of - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - precedes - x precedes y if and only if the time point at which x ends is before or equivalent to the time point at which y starts. Formally: x precedes y iff ω(x) <= α(y), where α is a function that maps a process to a start point, and ω is a function that maps a process to an end point. - BFO:0000063 - uberon - precedes - - precedes - precedes - - - - - - - - - David Osumi-Sutherland - ends_at_start_of - meets - - - X immediately_precedes_Y iff: end(X) simultaneous_with start(Y) - immediately precedes - - - - - - - - - - Chris Mungall - A relation that holds between two occurrents. This is a grouping relation that collects together all the Allen relations. - temporally related to - - - - - - - - - - - - - - entity - - - - - - - - - occurrent - - - - - - - - - continuant - - - - - - - - - Jeff Lerman - 2023-03-29T00:33:04Z - This is a comment which will be a fallback definition. - chair - - - - - - - - - Jeff Lerman - 2023-03-29T00:32:55Z - furniture item - - - - - - - - - Jeff Lerman - 2023-03-29T00:33:24Z - office chair - - - - - - - - - Jeff Lerman - 2023-03-29T00:32:46Z - table - - - - - - - diff --git a/test/test_namespace/test_namespacemanager.py b/test/test_namespace/test_namespacemanager.py index 979d51601..d66f0dc98 100644 --- a/test/test_namespace/test_namespacemanager.py +++ b/test/test_namespace/test_namespacemanager.py @@ -5,6 +5,7 @@ import sys from contextlib import ExitStack from pathlib import Path +from test.utils.exceptions import ExceptionChecker from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Set, Tuple, Type, Union import pytest @@ -486,67 +487,100 @@ def check() -> None: check() +def make_test_nsm() -> NamespaceManager: + namespaces = [ + ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), + ("", "http://example.org/"), + ( + # Because of this + # will have no effect on the namespace manager. + "eg", + "http://example.org/", + ), + ] + graph = Graph(bind_namespaces="none") + for prefix, namespace in namespaces: + graph.bind(prefix, namespace, override=False) + + return graph.namespace_manager + + +@pytest.fixture(scope="session") +def test_nsm_session() -> NamespaceManager: + return make_test_nsm() + + +@pytest.fixture(scope="function") +def test_nsm_function() -> NamespaceManager: + return make_test_nsm() + + @pytest.mark.parametrize( - "curie, expected_uri, expected_exception, expected_exc_patt", + ["curie", "expected_result"], [ - ("rdf:type", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", None, ""), - ("obo:IAO_0000111", "http://purl.obolibrary.org/obo/IAO_0000111", None, ""), - ("obo:nonexistent", "http://purl.obolibrary.org/obo/nonexistent", None, ""), - ("too_small", "irrelevant", ValueError, "Malformed curie argument"), + ("rdf:type", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + (":foo", "http://example.org/foo"), + ("too_small", ExceptionChecker(ValueError, "Malformed curie argument")), ( - "blah:chair", - "http://blah.org/ontology#chair", - ValueError, - 'Prefix "blah" not bound to any namespace', + "egdo:bar", + ExceptionChecker(ValueError, 'Prefix "egdo" not bound to any namespace'), ), - # next case only works with fix for https://github.com/RDFLib/rdflib/issues/2348 - (":chair", "http://www.example.org/ontologies/mini-ont#chair", None, ""), pytest.param( - # failure case that should succeed once https://github.com/RDFLib/rdflib/issues/2077 is fixed - "mini-ont:chair", - "http://www.example.org/ontologies/mini-ont#chair", - None, - "", - marks=pytest.mark.xfail, + "eg:foo", + "http://example.org/foo", + marks=pytest.mark.xfail( + raises=ValueError, + reason="This is failing because of https://github.com/RDFLib/rdflib/issues/2077", + ), ), ], ) def test_expand_curie( - test_owl_graph: Graph, + test_nsm_session: NamespaceManager, curie: str, - expected_uri: str, - expected_exception: Optional[Type[Exception]], - expected_exc_patt: str, -): - """Confirm that NamespaceManager.expand_curie() handles various CURIEs correctly.""" - nsm = test_owl_graph.namespace_manager - if expected_exception is None: - actual_uri = nsm.expand_curie(curie) - assert actual_uri == URIRef(expected_uri) - else: - with pytest.raises( - expected_exception=expected_exception, match=expected_exc_patt - ): - _ = nsm.expand_curie(curie) + expected_result: Union[ExceptionChecker, str], +) -> None: + nsm = test_nsm_session + with ExitStack() as xstack: + if isinstance(expected_result, ExceptionChecker): + xstack.enter_context(expected_result) + result = nsm.expand_curie(curie) + + if not isinstance(expected_result, ExceptionChecker): + assert URIRef(expected_result) == result @pytest.mark.parametrize( - "uri, expected_curie", + ["uri", "expected_result"], [ ("http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "rdf:type"), - ("http://purl.obolibrary.org/obo/BFO_0000002", "obo:BFO_0000002"), - ("http://www.example.org/ontologies/mini-ont#chair", ":chair"), - ("http://bogus.org/our_ontology#chair", "ns1:chair"), - ("http://bogus.org/their_ontology#chair", "ns2:chair"), + ("http://example.org/foo", ":foo"), + ("http://example.com/a#chair", "ns1:chair"), + ("http://example.com/b#chair", "ns1:chair"), + ("http://example.com/c", "ns1:c"), + ("", ExceptionChecker(ValueError, "Can't split ''")), + ( + "http://example.com/", + ExceptionChecker(ValueError, "Can't split 'http://example.com/'"), + ), ], ) -def test_generate_curie(test_owl_graph: Graph, uri: str, expected_curie: str): - """Confirm that NamespaceManager.curie() generates the expected CURIE given a URI. +def test_generate_curie( + test_nsm_function: NamespaceManager, + uri: str, + expected_result: Union[ExceptionChecker, str], +) -> None: + """ + .. note:: - Includes demonstration that unknown namespaces are auto-populated into the - NamespaceManager, and that entities in the default namespace get a CURIE that starts - with a colon. + This is using the function scoped nsm fixture because curie has side + effects and will modify the namespace manager. """ - nsm = test_owl_graph.namespace_manager - actual_curie = nsm.curie(uri) - assert actual_curie == expected_curie + nsm = test_nsm_function + with ExitStack() as xstack: + if isinstance(expected_result, ExceptionChecker): + xstack.enter_context(expected_result) + result = nsm.curie(uri) + + if not isinstance(expected_result, ExceptionChecker): + assert expected_result == result diff --git a/test/utils/exceptions.py b/test/utils/exceptions.py index a814f9b40..94cfd9c29 100644 --- a/test/utils/exceptions.py +++ b/test/utils/exceptions.py @@ -1,15 +1,32 @@ +from __future__ import annotations + import logging import re from dataclasses import dataclass -from typing import Any, Dict, Optional, Pattern, Type, Union +from types import TracebackType +from typing import Any, ContextManager, Dict, Optional, Pattern, Type, Union + +import pytest +from pytest import ExceptionInfo -@dataclass(frozen=True) -class ExceptionChecker: +@dataclass +class ExceptionChecker(ContextManager[ExceptionInfo[Exception]]): type: Type[Exception] pattern: Optional[Union[Pattern[str], str]] = None attributes: Optional[Dict[str, Any]] = None + def __post_init__(self) -> None: + self._catcher = pytest.raises(self.type, match=self.pattern) + self._exception_info: Optional[ExceptionInfo[Exception]] = None + + def _check_attributes(self, exception: Exception) -> None: + if self.attributes is not None: + for key, value in self.attributes.items(): + logging.debug("checking exception attribute %s=%r", key, value) + assert hasattr(exception, key) + assert getattr(exception, key) == value + def check(self, exception: Exception) -> None: logging.debug("checking exception %s/%r", type(exception), exception) pattern = self.pattern @@ -19,11 +36,22 @@ def check(self, exception: Exception) -> None: assert isinstance(exception, self.type) if pattern is not None: assert pattern.match(f"{exception}") - if self.attributes is not None: - for key, value in self.attributes.items(): - logging.debug("checking exception attribute %s=%r", key, value) - assert hasattr(exception, key) - assert getattr(exception, key) == value + self._check_attributes(exception) except Exception: logging.error("problem checking exception", exc_info=exception) raise + + def __enter__(self) -> ExceptionInfo[Exception]: + self._exception_info = self._catcher.__enter__() + return self._exception_info + + def __exit__( + self, + __exc_type: Optional[Type[BaseException]], + __exc_value: Optional[BaseException], + __traceback: Optional[TracebackType], + ) -> bool: + result = self._catcher.__exit__(__exc_type, __exc_value, __traceback) + if self._exception_info is not None: + self._check_attributes(self._exception_info.value) + return result