Coverage for src / sql_tool / formatters / base.py: 90%

20 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-14 15:28 -0500

1"""Formatter protocol and registry for output formatting.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Protocol, runtime_checkable 

6 

7if TYPE_CHECKING: 

8 from collections.abc import Iterator 

9 

10 from sql_tool.core.models import QueryResult 

11 

12 

13@runtime_checkable 

14class Formatter(Protocol): 

15 """Protocol for output formatters. 

16 

17 Each formatter transforms a QueryResult into lines of formatted text. 

18 Yielding strings (rather than returning a single string) enables 

19 streaming output for large result sets without buffering everything 

20 in memory. 

21 """ 

22 

23 def format(self, result: QueryResult) -> Iterator[str]: 

24 """Transform a QueryResult into formatted output lines.""" 

25 ... 

26 

27 

28class FormatterRegistry: 

29 """Registry for looking up formatters by name.""" 

30 

31 def __init__(self) -> None: 

32 self._formatters: dict[str, type[Formatter]] = {} 

33 

34 def register(self, name: str, formatter_class: type[Formatter]) -> None: 

35 self._formatters[name] = formatter_class 

36 

37 def get(self, name: str, **kwargs: object) -> Formatter: 

38 """Return a formatter instance by name. 

39 

40 Raises KeyError if the format name is not registered. 

41 """ 

42 if name not in self._formatters: 

43 available = ", ".join(sorted(self._formatters)) 

44 msg = f"Unknown format {name!r}. Available: {available}" 

45 raise KeyError(msg) 

46 return self._formatters[name](**kwargs) 

47 

48 @property 

49 def available(self) -> list[str]: 

50 return sorted(self._formatters) 

51 

52 

53# Global registry instance populated by formatter modules. 

54registry = FormatterRegistry()