163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
"""Client for the external LogStorm attendance analyzer API."""
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
from typing import Any, Optional
|
|
|
|
import requests
|
|
from django.conf import settings
|
|
|
|
|
|
class LogStormClientError(RuntimeError):
|
|
"""Raised when LogStorm cannot process an attendance request."""
|
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
|
super().__init__(message)
|
|
self.status_code = status_code
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LogStormConfig:
|
|
base_url: str
|
|
token: str = ""
|
|
timeout: int = 30
|
|
|
|
|
|
def load_logstorm_config() -> LogStormConfig:
|
|
return LogStormConfig(
|
|
base_url=getattr(settings, "LOGSTORM_API_URL", "http://127.0.0.1:8000").rstrip(
|
|
"/"
|
|
),
|
|
token=getattr(settings, "LOGSTORM_API_TOKEN", ""),
|
|
timeout=getattr(settings, "LOGSTORM_TIMEOUT_SECONDS", 30),
|
|
)
|
|
|
|
|
|
class LogStormClient:
|
|
"""Small synchronous client for EUSRR -> LogStorm calls."""
|
|
|
|
def __init__(
|
|
self,
|
|
config: Optional[LogStormConfig] = None,
|
|
session: Optional[requests.Session] = None,
|
|
):
|
|
self.config = config or load_logstorm_config()
|
|
self.session = session or requests.Session()
|
|
|
|
def analyze_attendance(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
response = self.session.post(
|
|
self._url("/attendance/analyze"),
|
|
json=payload,
|
|
headers=self._headers(),
|
|
timeout=self.config.timeout,
|
|
)
|
|
if not response.ok:
|
|
raise LogStormClientError(
|
|
f"LogStorm API returned HTTP {response.status_code}: {response.text}",
|
|
status_code=response.status_code,
|
|
)
|
|
try:
|
|
return response.json()
|
|
except ValueError as exc:
|
|
raise LogStormClientError("LogStorm API returned invalid JSON") from exc
|
|
|
|
def update_attendance_override(
|
|
self,
|
|
*,
|
|
employee_id: str,
|
|
record_date: date | str,
|
|
payload: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
response = self.session.patch(
|
|
self._url(f"/attendance/overrides/{employee_id}/{record_date}"),
|
|
json=payload,
|
|
headers=self._headers(),
|
|
timeout=self.config.timeout,
|
|
)
|
|
if not response.ok:
|
|
raise LogStormClientError(
|
|
f"LogStorm API returned HTTP {response.status_code}: {response.text}",
|
|
status_code=response.status_code,
|
|
)
|
|
try:
|
|
return response.json()
|
|
except ValueError as exc:
|
|
raise LogStormClientError("LogStorm API returned invalid JSON") from exc
|
|
|
|
def get_attendance_day_events(
|
|
self,
|
|
*,
|
|
employee_id: str,
|
|
record_date: date | str,
|
|
aliases: Optional[list[Any]] = None,
|
|
) -> list[dict[str, Any]]:
|
|
params: dict[str, Any] = {
|
|
"employee_id": str(employee_id),
|
|
"date": str(record_date),
|
|
}
|
|
normalized_aliases = _normalize_aliases(aliases)
|
|
if normalized_aliases:
|
|
params["aliases"] = normalized_aliases
|
|
response = self.session.get(
|
|
self._url("/attendance/events/day/"),
|
|
params=params,
|
|
headers=self._headers(),
|
|
timeout=self.config.timeout,
|
|
)
|
|
if not response.ok:
|
|
raise LogStormClientError(
|
|
f"LogStorm API returned HTTP {response.status_code}: {response.text}",
|
|
status_code=response.status_code,
|
|
)
|
|
try:
|
|
payload = response.json()
|
|
except ValueError as exc:
|
|
raise LogStormClientError("LogStorm API returned invalid JSON") from exc
|
|
if not isinstance(payload, list):
|
|
raise LogStormClientError("LogStorm API returned invalid events payload")
|
|
return payload
|
|
|
|
def get_attendance_event_photo(self, event_key: str) -> requests.Response:
|
|
headers = self._headers()
|
|
headers["Accept"] = "image/*"
|
|
response = self.session.get(
|
|
self._url(f"/attendance/events/photos/{event_key}/"),
|
|
headers=headers,
|
|
timeout=self.config.timeout,
|
|
)
|
|
if not response.ok:
|
|
raise LogStormClientError(
|
|
f"LogStorm API returned HTTP {response.status_code}: {response.text}",
|
|
status_code=response.status_code,
|
|
)
|
|
return response
|
|
|
|
def health(self) -> bool:
|
|
try:
|
|
response = self.session.get(
|
|
self._url("/health"),
|
|
headers=self._headers(),
|
|
timeout=self.config.timeout,
|
|
)
|
|
except requests.RequestException:
|
|
return False
|
|
return response.ok
|
|
|
|
def _url(self, path: str) -> str:
|
|
return f"{self.config.base_url}/{path.lstrip('/')}"
|
|
|
|
def _headers(self) -> dict[str, str]:
|
|
headers = {"Accept": "application/json"}
|
|
if self.config.token:
|
|
headers["Authorization"] = f"Bearer {self.config.token}"
|
|
return headers
|
|
|
|
|
|
def _normalize_aliases(aliases: Optional[list[Any]]) -> list[str]:
|
|
result = []
|
|
for alias in aliases or []:
|
|
alias_value = str(alias).strip()
|
|
if alias_value and alias_value not in result:
|
|
result.append(alias_value)
|
|
return result
|