clarke / backend /utils.py
yashvshetty's picture
Clarke: NHS clinical documentation system
9636a02
"""Shared utility helpers used across backend modules."""
from __future__ import annotations
import json
import time
from collections.abc import Callable
from functools import wraps
from typing import Any
from backend.errors import get_component_logger
def timed(component: str) -> Callable:
"""Measure function runtime and log duration with structured metadata.
Params:
component (str): Component name used in structured logs.
Returns:
Callable: Decorator wrapping the target callable.
"""
log = get_component_logger(component)
def decorator(func: Callable) -> Callable:
"""Wrap a function with timing instrumentation.
Params:
func (Callable): Function to time.
Returns:
Callable: Timed wrapper preserving function signature metadata.
"""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
"""Execute wrapped function and emit success/failure timing logs.
Params:
*args (Any): Positional arguments passed to wrapped function.
**kwargs (Any): Keyword arguments passed to wrapped function.
Returns:
Any: Original function return value.
"""
start_time = time.perf_counter()
try:
result = func(*args, **kwargs)
duration_s = time.perf_counter() - start_time
log.info(
"Function execution complete",
function_name=func.__name__,
duration_s=round(duration_s, 4),
)
return result
except Exception:
duration_s = time.perf_counter() - start_time
log.exception(
"Function execution failed",
function_name=func.__name__,
duration_s=round(duration_s, 4),
)
raise
return wrapper
return decorator
def sanitize_json_payload(payload: Any) -> Any:
"""Return a JSON-serialisable deep copy of an arbitrary payload.
Params:
payload (Any): Input object that should be JSON serialisable.
Returns:
Any: Normalised payload safe for JSON transport/storage.
"""
return json.loads(json.dumps(payload, default=str))