from abc import ABC, abstractmethod class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def _printer(self, data): print(*("{: >10}".format(value) for value in data)) def headings(self, headers): self._printer(headers) print(*("{:->10}".format("") for _ in headers)) def row(self, rowdata): self._printer(rowdata) class CSVTableFormatter(TableFormatter): def _printer(self, data): print(",".join(str(value) for value in data)) def headings(self, headers): return self._printer(headers) def row(self, rowdata): return self._printer(rowdata) class HTMLTableFormatter(TableFormatter): def _cell(self, value, tag): return f"<{tag}>{value}" def _printer(self, data, tag): line = f"{' '.join(self._cell(str(value), tag) for value in data)}" print(line) def headings(self, headers): return self._printer(headers, "th") def row(self, rowdata): return self._printer(rowdata, "td") class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): formatters = { "text": TextTableFormatter, "csv": CSVTableFormatter, "html": HTMLTableFormatter, } formatter = formatters.get(name, None) if not name: raise ValueError(f'formatter named "{name}" not implemented') if upper_headers: class _UpperFormatter(UpperHeadersMixin, formatter): pass formatter = _UpperFormatter if column_formats: class _ColumnFormatter(ColumnFormatMixin, formatter): formats = column_formats formatter = _ColumnFormatter return formatter() def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise TypeError("expected a TableFormatter") formatter.headings(fields) for record in records: formatter.row([getattr(record, fieldname) for fieldname in fields])