Coverage for heritrace/routes/linked_resources.py: 100%
59 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-06-24 11:39 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-06-24 11:39 +0000
1import traceback
3from flask import Blueprint, current_app, jsonify, request
4from flask_babel import gettext
5from flask_login import login_required
6from heritrace.extensions import get_custom_filter, get_sparql
7from heritrace.utils.display_rules_utils import get_highest_priority_class
8from heritrace.utils.shacl_utils import determine_shape_for_classes
9from heritrace.utils.sparql_utils import get_entity_types
10from heritrace.utils.virtuoso_utils import (VIRTUOSO_EXCLUDED_GRAPHS,
11 is_virtuoso)
12from SPARQLWrapper import JSON
14linked_resources_bp = Blueprint("linked_resources", __name__, url_prefix="/api/linked-resources")
16def get_paginated_inverse_references(subject_uri: str, limit: int, offset: int) -> tuple[list[dict], bool]:
17 """
18 Get paginated entities that reference this entity using the limit+1 strategy.
20 Args:
21 subject_uri: URI of the entity to find references to.
22 limit: Maximum number of references to return per page.
23 offset: Number of references to skip.
25 Returns:
26 A tuple containing:
27 - List of dictionaries containing reference information (max 'limit' items).
28 - Boolean indicating if there are more references.
29 """
30 sparql = get_sparql()
31 custom_filter = get_custom_filter()
32 references = []
33 # Fetch limit + 1 to check if there are more results
34 query_limit = limit + 1
36 try:
37 # Main Query with pagination (limit + 1)
38 query_parts = [
39 "SELECT DISTINCT ?s ?p WHERE {",
40 ]
41 if is_virtuoso:
42 query_parts.append(" GRAPH ?g { ?s ?p ?o . }")
43 query_parts.append(f" FILTER(?g NOT IN (<{'>, <'.join(VIRTUOSO_EXCLUDED_GRAPHS)}>))")
44 else:
45 query_parts.append(" ?s ?p ?o .")
47 query_parts.extend([
48 f" FILTER(?o = <{subject_uri}>)",
49 " FILTER(?p != <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>)",
50 f"}} ORDER BY ?s OFFSET {offset} LIMIT {query_limit}" # Use query_limit
51 ])
52 main_query = "\n".join(query_parts)
54 sparql.setQuery(main_query)
55 sparql.setReturnFormat(JSON)
56 results = sparql.query().convert()
58 bindings = results.get("results", {}).get("bindings", [])
60 # Determine if there are more results
61 has_more = len(bindings) > limit
63 # Process only up to 'limit' results
64 results_to_process = bindings[:limit]
66 for result in results_to_process:
67 subject = result["s"]["value"]
68 predicate = result["p"]["value"]
70 types = get_entity_types(subject)
71 highest_priority_type = get_highest_priority_class(types)
72 shape = determine_shape_for_classes(types)
73 label = custom_filter.human_readable_entity(subject, (highest_priority_type, shape))
74 type_label = custom_filter.human_readable_class((highest_priority_type, shape)) if highest_priority_type else None
76 references.append({
77 "subject": subject,
78 "predicate": predicate,
79 "predicate_label": custom_filter.human_readable_predicate(predicate, (highest_priority_type, shape)),
80 "type_label": type_label,
81 "label": label
82 })
84 return references, has_more
86 except Exception as e:
87 tb_str = traceback.format_exc()
88 current_app.logger.error(f"Error fetching inverse references for {subject_uri}: {e}\n{tb_str}")
89 return [], False
91@linked_resources_bp.route("/", methods=["GET"])
92@login_required
93def get_linked_resources_api():
94 """API endpoint to fetch paginated linked resources (inverse references)."""
95 subject_uri = request.args.get("subject_uri")
96 try:
97 limit = int(request.args.get("limit", 5))
98 offset = int(request.args.get("offset", 0))
99 except ValueError:
100 return jsonify({"status": "error", "message": gettext("Invalid limit or offset parameter")}), 400
102 if not subject_uri:
103 return jsonify({"status": "error", "message": gettext("Missing subject_uri parameter")}), 400
105 if limit <= 0 or offset < 0:
106 return jsonify({"status": "error", "message": gettext("Limit must be positive and offset non-negative")}), 400
108 references, has_more = get_paginated_inverse_references(subject_uri, limit, offset)
110 return jsonify({
111 "status": "success",
112 "results": references,
113 "has_more": has_more
114 })