Skip to content

Commit a24586a

Browse files
mwatts15aucampia
authored andcommitted
Logging exceptions from Literal value converters
- Also, adding a function to reset bindings so tests are less order-dependent
1 parent ba855c2 commit a24586a

File tree

2 files changed

+73
-17
lines changed

2 files changed

+73
-17
lines changed

rdflib/term.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,8 @@ def _castPythonToLiteral( # noqa: N802
19621962
(Fraction, (None, _OWL_RATIONAL)),
19631963
]
19641964

1965+
_OriginalGenericPythonToXSDRules = list(_GenericPythonToXSDRules)
1966+
19651967
_SpecificPythonToXSDRules: List[
19661968
Tuple[Tuple[Type[Any], str], Optional[Callable[[Any], Union[str, bytes]]]]
19671969
] = [
@@ -1973,6 +1975,8 @@ def _castPythonToLiteral( # noqa: N802
19731975
((bytes, _XSD_B64BINARY), b64encode),
19741976
]
19751977

1978+
_OriginalSpecificPythonToXSDRules = list(_SpecificPythonToXSDRules)
1979+
19761980
XSDToPython: Dict[Optional[str], Optional[Callable[[str], Any]]] = {
19771981
None: None, # plain literals map directly to value space
19781982
URIRef(_XSD_PFX + "time"): parse_time,
@@ -2031,32 +2035,52 @@ def _castPythonToLiteral( # noqa: N802
20312035
_toPythonMapping.update(XSDToPython)
20322036

20332037

2038+
def _reset_bindings() -> None:
2039+
"""
2040+
Reset lexical<->value space binding for `Literal`
2041+
"""
2042+
_toPythonMapping.clear()
2043+
_toPythonMapping.update(XSDToPython)
2044+
2045+
_GenericPythonToXSDRules.clear()
2046+
_GenericPythonToXSDRules.extend(_OriginalGenericPythonToXSDRules)
2047+
2048+
_SpecificPythonToXSDRules.clear()
2049+
_SpecificPythonToXSDRules.extend(_OriginalSpecificPythonToXSDRules)
2050+
2051+
20342052
def _castLexicalToPython( # noqa: N802
20352053
lexical: Union[str, bytes], datatype: Optional[str]
20362054
) -> Any:
20372055
"""
20382056
Map a lexical form to the value-space for the given datatype
20392057
:returns: a python object for the value or ``None``
20402058
"""
2041-
convFunc = _toPythonMapping.get(datatype, False) # noqa: N806
2042-
if convFunc:
2043-
if TYPE_CHECKING:
2044-
# NOTE: This is here because convFunc is seen as
2045-
# Union[Callable[[str], Any], bool, None]
2046-
# even though it will never have value of True.
2047-
assert not isinstance(convFunc, bool)
2048-
convFunc
2059+
try:
2060+
conv_func = _toPythonMapping[datatype]
2061+
except KeyError:
2062+
# no conv_func -> unknown data-type
2063+
return None
2064+
2065+
if conv_func is not None:
20492066
try:
20502067
# type error: Argument 1 has incompatible type "Union[str, bytes]"; expected "str"
20512068
# NOTE for type ignore: various functions in _toPythonMapping will
20522069
# only work for str, so there is some inconsistency here, the right
20532070
# approach may be to change lexical to be of str type but this will
20542071
# require runtime changes.
2055-
return convFunc(lexical) # type: ignore[arg-type]
2056-
except:
2072+
return conv_func(lexical) # type: ignore[arg-type]
2073+
except Exception:
2074+
logger.warning(
2075+
"Failed to convert Literal lexical form to value. Datatype=%s, "
2076+
"Converter=%s",
2077+
datatype,
2078+
conv_func,
2079+
exc_info=True,
2080+
)
20572081
# not a valid lexical representation for this dt
20582082
return None
2059-
elif convFunc is None:
2083+
else:
20602084
# no conv func means 1-1 lexical<->value-space mapping
20612085
try:
20622086
return str(lexical)
@@ -2065,9 +2089,6 @@ def _castLexicalToPython( # noqa: N802
20652089
# NOTE for type ignore: code assumes that lexical is of type bytes
20662090
# at this point.
20672091
return str(lexical, "utf-8") # type: ignore[arg-type]
2068-
else:
2069-
# no convFunc - unknown data-type
2070-
return None
20712092

20722093

20732094
_AnyT = TypeVar("_AnyT", bound=Any)

test/test_literal/test_literal.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from contextlib import ExitStack
1313
from decimal import Decimal
1414
from test.utils import affix_tuples
15-
from typing import Any, Optional, Type, Union
15+
from typing import Any, Generator, Optional, Type, Union
1616

1717
import isodate
1818
import pytest
@@ -33,12 +33,21 @@
3333
_XSD_TIME,
3434
Literal,
3535
URIRef,
36+
_reset_bindings,
3637
bind,
3738
)
3839

3940
EGNS = Namespace("http://example.com/")
4041

4142

43+
@pytest.fixture()
44+
def clear_bindings() -> Generator[None, None, None]:
45+
try:
46+
yield
47+
finally:
48+
_reset_bindings()
49+
50+
4251
class TestLiteral:
4352
def test_repr_apostrophe(self) -> None:
4453
a = rdflib.Literal("'")
@@ -779,7 +788,7 @@ def test_non_false_boolean(self) -> None:
779788

780789

781790
class TestBindings:
782-
def test_binding(self) -> None:
791+
def test_binding(self, clear_bindings: None) -> None:
783792
class a:
784793
def __init__(self, v: str) -> None:
785794
self.v = v[3:-3]
@@ -814,7 +823,7 @@ def __str__(self) -> str:
814823
assert lb.value == vb
815824
assert lb.datatype == dtB
816825

817-
def test_specific_binding(self) -> None:
826+
def test_specific_binding(self, clear_bindings: None) -> None:
818827
def lexify(s: str) -> str:
819828
return "--%s--" % s
820829

@@ -927,3 +936,29 @@ def check_make_literals(
927936
else:
928937
assert literal.value is None
929938
assert lexical == f"{literal}"
939+
940+
941+
def test_exception_in_converter(
942+
caplog: pytest.LogCaptureFixture, clear_bindings: None
943+
) -> None:
944+
def lexify(s: str) -> str:
945+
return "--%s--" % s
946+
947+
def unlexify(s: str) -> str:
948+
raise Exception("TEST_EXCEPTION")
949+
950+
datatype = rdflib.URIRef("urn:dt:mystring")
951+
952+
# Datatype-specific rule
953+
bind(datatype, str, unlexify, lexify, datatype_specific=True)
954+
955+
s = "Hello"
956+
957+
Literal("--%s--" % s, datatype=datatype)
958+
959+
assert (
960+
caplog.record_tuples[0][1] == logging.WARNING
961+
and caplog.record_tuples[0][2].startswith("Failed to convert")
962+
and caplog.records[0].exc_info
963+
and str(caplog.records[0].exc_info[1]) == "TEST_EXCEPTION"
964+
)

0 commit comments

Comments
 (0)