from abc import ABC, abstractmethod class TableFormatter(ABC): _formats = {} @classmethod def __init_subclass__(cls): name = cls.__module__.split('.')[-1] TableFormatter._formats[name] = cls @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass 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): if not name: raise ValueError(f'formatter named "{name}" not implemented') if name not in TableFormatter._formats: __import__(f'{__package__}.{name}') formatter = TableFormatter._formats.get(name) if not formatter: raise RuntimeError(f'Unknown format {name}') 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])