Coverage for src/backoffice/utils_pure.py: 46%
52 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-12 10:26 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-12 10:26 +0000
1"""utility functions available in backoffice without dependencies"""
3import json
4import os
5from pathlib import Path
6from typing import TYPE_CHECKING, Any, Optional
8try:
9 import dotenv
10except ImportError:
11 pass
12else:
13 _ = dotenv.load_dotenv()
15if TYPE_CHECKING:
16 import httpx
19def get_report_path(
20 item_id: str,
21 version: str,
22) -> Path:
23 return Path(os.getenv("REPORTS", "reports")) / item_id.replace(":", "_") / version
26def get_tool_report_path(
27 item_id: str,
28 version: str,
29 tool_name: str,
30 tool_version: str,
31):
32 """Get the path to the report for a specific item version and tool."""
33 if "_" in tool_name:
34 raise ValueError("Underscore not allowed in tool_name")
36 if "_" in tool_version:
37 raise ValueError("Underscore not allowed in tool_version")
39 return (
40 get_report_path(item_id, version)
41 / "reports"
42 / f"{tool_name}_{tool_version}.json"
43 )
46def get_all_tool_report_paths(
47 item_id: str,
48 version: str,
49):
50 return list((get_report_path(item_id, version) / "reports").glob("*.json"))
53def get_summary_data(item_id: str, version: str) -> Optional[dict[str, Any]]:
54 """Get the summary data of a specific item version."""
55 summary_file_path = get_summary_file_path(item_id, version)
56 if not summary_file_path.exists():
57 return None
59 with summary_file_path.open(encoding="utf-8") as f:
60 return json.load(f)
63def get_summary_file_path(item_id: str, version: str) -> Path:
64 return get_report_path(item_id, version) / "summary.json"
67def get_log_file(item_id: str, version: str) -> Path:
68 return get_report_path(item_id, version) / "log.txt"
71def cached_download(url: str, sha256: str) -> Path:
72 """Download a file from the given URL and cache it locally."""
73 import httpx
75 local_path = Path("cache") / sha256
76 if not local_path.exists():
77 local_path.parent.mkdir(parents=True, exist_ok=True)
78 response = httpx.get(
79 url, timeout=float(os.environ.get("HTTP_TIMEOUT", "30"))
80 ).raise_for_status()
81 with local_path.open("wb") as f:
82 _ = f.write(response.content)
84 return local_path
87def get_rdf_content_from_id(item_id: str, version: str) -> dict[str, Any]:
88 """Get the RDF file content of a specific item version."""
89 with get_summary_file_path(item_id, version).open() as f:
90 return json.load(f)["rdf_content"]
93def raise_for_status_discretely(response: "httpx.Response"):
94 """Raises :class:`httpx.HTTPError` for 4xx or 5xx responses,
95 **but** hides any query and userinfo from url to avoid leaking sensitive data.
96 """
97 import httpx
99 http_error_msg = ""
100 reason = response.reason_phrase
102 discrete_url = response.url.copy_with(
103 query=(b"***query*hidden***" if response.url.query else b""),
104 userinfo=(b"***userinfo*hidden***" if response.url.userinfo else b""),
105 )
107 if 400 <= response.status_code < 500:
108 http_error_msg = (
109 f"{response.status_code} Client Error: {reason} for url: {discrete_url}"
110 )
112 elif 500 <= response.status_code < 600:
113 http_error_msg = (
114 f"{response.status_code} Server Error: {reason} for url: {discrete_url}"
115 )
117 if http_error_msg:
118 raise httpx.HTTPError(http_error_msg)