I recently found myself having to prepare a report of some mortgage calculations so that non-technical domain experts could read it, evaluate it, and tell me whether my math and the way I was using certain APIs was correct.
Since I’m using Python, I decided to go as native as possible and make my little script generate a ReStructured Text file that I would then convert into HTML, PDFs, whatever. The result of certain calculations ended up looking like a data table expressed as list of dicts all with the same keys. I wrote a function that would turn that list of dicts into the appropriately formatted ReStructured Text.
For example, given this data:
creators = [{"name": "Guido van Rossum", "language": "Python"},
{"name": "Alan Kay", "language": "Smalltalk"},
{"name": "John McCarthy", "language": "Lisp"}]
when you call it with:
dict_to_rst_table(creators)
it produces:
+------------------+-----------+
| name | language |
+==================+===========+
| Guido van Rossum | Python |
+------------------+-----------+
| Alan Kay | Smalltalk |
+------------------+-----------+
| John McCarthy | Lisp |
+------------------+-----------+
The full code for this is:
from collections import defaultdict
from io import StringIO
def dict_to_rst_table(data):
field_names, column_widths = _get_fields(data)
with StringIO() as output:
output.write(_generate_header(field_names, column_widths))
for row in data:
output.write(_generate_row(row, field_names, column_widths))
return output.getvalue()
def _generate_header(field_names, column_widths):
with StringIO() as output:
for field_name in field_names:
output.write(f"+-{'-' * column_widths[field_name]}-")
output.write("+\n")
for field_name in field_names:
output.write(f"| {field_name} {' ' * (column_widths[field_name] - len(field_name))}")
output.write("|\n")
for field_name in field_names:
output.write(f"+={'=' * column_widths[field_name]}=")
output.write("+\n")
return output.getvalue()
def _generate_row(row, field_names, column_widths):
with StringIO() as output:
for field_name in field_names:
output.write(f"| {row[field_name]}{' ' * (column_widths[field_name] - len(str(row[field_name])))} ")
output.write("|\n")
for field_name in field_names:
output.write(f"+-{'-' * column_widths[field_name]}-")
output.write("+\n")
return output.getvalue()
def _get_fields(data):
field_names = []
column_widths = defaultdict(lambda: 0)
for row in data:
for field_name in row:
if field_name not in field_names:
field_names.append(field_name)
column_widths[field_name] = max(column_widths[field_name], len(field_name), len(str(row[field_name])))
return field_names, column_widths
Feel free to use it as you see fit, and if you’d like this to be a nicely tested reusable pip package, let me know and I’ll turn it to one. One thing that I would need to add is making it more robust to malformed data and handle more cases of data that looks differently.
If I turn it into a pip package, it would be released from Eligible, as I wrote this code while working there and we are happy to contribute to open source.
Leave a Reply