Coverage for oc_ocdm / support / sparql.py: 72%
50 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-10 09:13 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-10 09:13 +0000
1# SPDX-FileCopyrightText: 2026 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5from __future__ import annotations
7import json
8import time
9from typing import cast
10from urllib.error import HTTPError, URLError
11from urllib.parse import parse_qs, urlparse
13from SPARQLWrapper import GET, JSON, N3, POST, URLENCODED, SPARQLWrapper
15from oc_ocdm._types import SparqlQueryResult
18class SPARQLEndpointError(Exception):
19 def __init__(self, message: str, status_code: int | None = None):
20 super().__init__(message)
21 self.status_code = status_code
24def _make_sparql_client(endpoint: str) -> SPARQLWrapper:
25 parsed = urlparse(endpoint)
26 base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
27 sparql = SPARQLWrapper(base_url)
28 for key, values in parse_qs(parsed.query).items():
29 sparql.addParameter(key, values[0])
30 return sparql
33def _execute_with_retry(
34 endpoint: str,
35 query: str,
36 return_format: str,
37 *,
38 method: str = GET,
39 max_retries: int = 5,
40 backoff_factor: float = 0.5,
41) -> bytes:
42 sparql = _make_sparql_client(endpoint)
43 sparql.setQuery(query)
44 sparql.setReturnFormat(return_format)
45 sparql.setMethod(method)
46 if method == POST:
47 sparql.setRequestMethod(URLENCODED)
49 last_error: SPARQLEndpointError | None = None
51 for attempt in range(max_retries + 1):
52 if attempt > 0:
53 time.sleep(backoff_factor * (2**attempt))
54 try:
55 return sparql.query().response.read()
56 except HTTPError as e:
57 if e.code == 400:
58 raise SPARQLEndpointError(f"Query syntax error: {e.read().decode()}", status_code=400) from e
59 if e.code >= 500:
60 last_error = SPARQLEndpointError(f"Server error: {e.code}", status_code=e.code)
61 continue
62 raise SPARQLEndpointError(f"HTTP error: {e.code} - {e.read().decode()}", status_code=e.code) from e
63 except URLError as e:
64 last_error = SPARQLEndpointError(f"Connection error: {e.reason}")
65 continue
67 raise last_error # type: ignore[misc]
70def sparql_query(
71 endpoint: str,
72 query: str,
73 *,
74 method: str = GET,
75 max_retries: int = 5,
76 backoff_factor: float = 0.5,
77) -> SparqlQueryResult:
78 raw = _execute_with_retry(
79 endpoint,
80 query,
81 JSON,
82 method=method,
83 max_retries=max_retries,
84 backoff_factor=backoff_factor,
85 )
86 return cast(SparqlQueryResult, json.loads(raw))
89def sparql_update(
90 endpoint: str,
91 query: str,
92 *,
93 max_retries: int = 5,
94 backoff_factor: float = 0.5,
95) -> None:
96 _execute_with_retry(endpoint, query, JSON, method=POST, max_retries=max_retries, backoff_factor=backoff_factor)
99def sparql_construct(
100 endpoint: str,
101 query: str,
102 *,
103 max_retries: int = 5,
104 backoff_factor: float = 0.5,
105) -> bytes:
106 return _execute_with_retry(endpoint, query, N3, max_retries=max_retries, backoff_factor=backoff_factor)