Coverage for src/metador_core/rdf/lib.py: 0%
65 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-02 09:33 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-02 09:33 +0000
1"""Wrappers and helpers for RDFlib."""
2from typing import Any, Dict, Union
4import rdflib
5import wrapt
6from rdflib.term import Identifier
8# NOTE: could evaluate oxrdflib if rdflib turns out being too slow
11def at_most_one(g):
12 """Return at most one element from a generator.
14 If there are more, will raise ValueError.
15 """
16 ret = next(g, None)
17 if next(g, None) is None:
18 return ret
19 else:
20 raise ValueError()
23class GraphNode(wrapt.ObjectProxy):
24 """Wrapper for rdflib helping to navigate entities in an `rdflib.Graph`."""
26 _self_graph: rdflib.Graph
27 obj: Identifier
29 def __init__(self, graph: rdflib.Graph, obj: Identifier = None):
30 super().__init__(obj)
31 self._self_graph = graph
33 @property
34 def graph(self):
35 return self._self_graph
37 @property
38 def node(self):
39 return self.__wrapped__
41 def wrap(self, obj: Identifier):
42 return GraphNode(self.graph, obj)
44 def is_literal(self):
45 return isinstance(self.__wrapped__, rdflib.Literal)
47 def is_uriref(self):
48 return isinstance(self.__wrapped__, rdflib.URIRef)
50 # ----
52 def edges_in(self):
53 return map(self.wrap, self.graph.predicates(object=self.node, unique=True)) # type: ignore
55 def edges_out(self):
56 return map(self.wrap, self.graph.predicates(subject=self.node, unique=True)) # type: ignore
58 def objects(self, pred):
59 return map(self.wrap, self.graph.objects(self.node, pred, unique=True)) # type: ignore
61 def subjects(self, pred):
62 return map(self.wrap, self.graph.subjects(pred, self.node, unique=True)) # type: ignore
64 def subject(self, pred):
65 try:
66 return at_most_one(self.subjects(pred))
67 except ValueError:
68 msg = f"Expected to get exactly one match for: (*, {pred}, {self.obj})"
69 raise ValueError(msg)
71 def object(self, pred):
72 try:
73 return at_most_one(self.objects(pred))
74 except ValueError:
75 msg = f"Expected to get exactly one match for: ({self.obj}, {pred}, *)"
76 raise ValueError(msg)
79class RDFParser(wrapt.ObjectProxy):
80 """Helper wrapper to access entity properties backed by an RDFlib graph.
82 Ensures that queries are only performed when needed (more efficient).
84 Use this e.g. to parse linked data with expected structure into your custom
85 data structures, e.g. for use in schemas.
86 """
88 __wrapped__: GraphNode
90 def __init__(self, node: GraphNode):
91 super().__init__(node)
92 self._self_parsed: Dict[str, Any] = {}
94 def __getattr__(self, key: str):
95 # special case: return the method
96 if key.startswith("parse_"):
97 return wrapt.ObjectProxy.__getattr__(self, key)
99 # try to return the attribute
100 if val := self._self_parsed.get(key):
101 return val
102 # need first to compute the attribute
103 if pfunc := getattr(self, f"parse_{key}"):
104 val = pfunc(self.__wrapped__)
105 self._self_parsed[key] = val
106 return val
107 # no parser defined -> pass through
108 return getattr(self.__wrapped__, key)
110 @classmethod
111 def from_graph(cls, g: rdflib.Graph, obj: Union[str, rdflib.URIRef]):
112 return cls(GraphNode(g, rdflib.URIRef(obj)))