From e32f8ff3e537bda306e7794fdce2b1305be621ff Mon Sep 17 00:00:00 2001 From: Birger Schacht Date: Wed, 18 Dec 2024 13:51:21 +0100 Subject: [PATCH] feat(generic): refactor GenericRDFBaseRenderer and add CIDOC renderers The GenericRDFBaseRenderer now uses graphs instead of working with triples. Additionaly it is possible to override the serialization format by setting the renderers `rdflib_format` attribute. If this is not set, the serialization uses the `format` attribute from the renderer as serialization format. --- apis_core/generic/renderers.py | 78 ++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/apis_core/generic/renderers.py b/apis_core/generic/renderers.py index 30a300ca6..696c994f4 100644 --- a/apis_core/generic/renderers.py +++ b/apis_core/generic/renderers.py @@ -2,32 +2,84 @@ from rdflib import Graph from rest_framework import renderers +from rest_framework.exceptions import APIException logger = logging.getLogger(__name__) class GenericRDFBaseRenderer(renderers.BaseRenderer): + """ + Base class to render RDF graphs to various formats. + This renderer expects the serialized data to either be a rdflib grap **or** + to contain a list of rdflib graphs. If it works with a list of graphs, those + are combined to one graph. + This graph is then serialized and the result is returned. The serialization + format can be set using the `rdflib_format` attribute. If this is not set, the + `format` attribute of the renderer is used as serialization format (this is the + format as it is used by the Django Rest Framework for content negotiation. + """ + + format = "ttl" + rdflib_format = None + def render(self, data, accepted_media_type=None, renderer_context=None): - g = Graph() - for result in data.get("results", []): - match result: - case tuple(_, _, _): - g.add(result) - case other: - logger.debug("Could not add %s to RDF graph: not a tuple", other) - return g.serialize(format=self.media_type) + result = Graph() + match data: + case {"results": results, **rest}: # noqa: F841 + # Handle case where data is a dict with multiple graphs + for graph in results: + if isinstance(graph, Graph): + # Merge triples + for triple in graph: + result.add(triple) + # Merge namespace bindings + for prefix, namespace in graph.namespaces(): + result.bind(prefix, namespace, override=False) + case {"detail": detail}: + raise APIException(detail) + case Graph(): + # Handle case where data is a single graph + result = data + # Ensure namespaces are properly bound in the single graph case + for prefix, namespace in data.namespaces(): + result.bind(prefix, namespace, override=False) + case _: + raise ValueError( + "Invalid data format. Expected rdflib Graph or dict with 'results' key containing graphs" + ) + return result.serialize(format=self.graph_format) -class GenericRDFXMLRenderer(GenericRDFBaseRenderer): - media_type = "application/rdf+xml" - format = "rdf+xml" + @property + def graph_format(self): + return self.rdflib_format or self.format class GenericRDFTurtleRenderer(GenericRDFBaseRenderer): + format = "ttl" media_type = "text/turtle" - format = "rdf+turtle" + rdflib_format = "turtle" + + +class GenericRDFXMLRenderer(GenericRDFBaseRenderer): + format = "rdf" + media_type = "application/rdf+xml" + rdflib_format = "xml" class GenericRDFN3Renderer(GenericRDFBaseRenderer): + format = "rdf" media_type = "text/n3" - format = "rdf+n3" + rdflib_format = "n3" + + +class CidocTTLRenderer(GenericRDFBaseRenderer): + format = "cidoc" + media_type = "text/ttl" + rdflib_format = "ttl" + + +class CidocXMLRenderer(GenericRDFBaseRenderer): + format = "cidoc" + media_type = "application/rdf+xml" + rdflib_format = "xml"