Coverage for src/jquantstats/_reports/_formatting.py: 100%

20 statements  

« prev     ^ index     » next       coverage.py v7.17.1, created at 2026-11-03 15:52 +0000

1"""Shared formatting helpers for report modules.""" 

2 

3from __future__ import annotations 

4 

5import math 

6from pathlib import Path 

7from typing import Any, TypeGuard 

8 

9import plotly.io as pio 

10from jinja2 import Environment, FileSystemLoader, select_autoescape 

11 

12# templates/ lives one level above this subpackage (at src/jquantstats/templates/) 

13_TEMPLATES_DIR = Path(__file__).parent.parent / "templates" 

14_env = Environment( 

15 loader=FileSystemLoader(_TEMPLATES_DIR), 

16 autoescape=select_autoescape(["html"]), 

17) 

18 

19 

20def _is_finite(v: Any) -> TypeGuard[int | float]: 

21 """Return True when *v* is a real, finite number.""" 

22 if not isinstance(v, (int, float)): 

23 return False 

24 return math.isfinite(float(v)) 

25 

26 

27def _fmt(value: Any, fmt: str = ".4f", suffix: str = "") -> str: 

28 """Format *value* for display; return ``"N/A"`` for non-finite values.""" 

29 if not _is_finite(value): 

30 return "N/A" 

31 return f"{float(value):{fmt}}{suffix}" 

32 

33 

34def _plotly_div(fig: Any, include_plotlyjs: bool | str = False) -> str: 

35 """Serialise a Plotly figure to a standalone HTML ``<div>``.""" 

36 return str(pio.to_html(fig, full_html=False, include_plotlyjs=include_plotlyjs)) 

37 

38 

39def _table_html(header_cells: str, body_html: str) -> str: 

40 """Wrap pre-rendered table cells and body rows in the shared report table shell.""" 

41 return ( 

42 '<table class="stats-table">' 

43 "<thead><tr>" 

44 f'<th class="metric-header">Metric</th>{header_cells}' 

45 "</tr></thead>" 

46 f"<tbody>{body_html}</tbody>" 

47 "</table>" 

48 )