I've got a list of dicts, data
, that updates periodically and is persisted to a database. Every time data
is updated, the gui needs to display the collated data in a table format. The columns of the table, in order, are TEST, RANGE and the dates of all possible dates that are represented in data
.
test
names may contain multiple words, upper and lower case characters, non alphanumeric characters and whitespace.
The range
key may contain None
, an empty string " ", or a 2 tuple of strings.
The rows of the table should be sorted alphabetically.
The date columns should be ordered chronologically as well.
Example of data in:
data = [
{'date': '201410171311', 'test': 'Test b', 'value': 30, 'range': ('10', '30')},
{'date': '201610310152', 'test': 'Test A', 'value': 6, 'range': '<=10'},
{'date': '201410171311', 'test': 'Test A', 'value': 8, 'range': '<=10'},
{'date': '201702062358', 'test': 'Test b', 'value': 15, 'range': ('10', '30')},
{'date': '201610310152', 'test': 'Test b', 'value': 20, 'range': ('10', '30')},
{'date': '201402162358', 'test': 'Test A', 'value': 3, 'range': '<=10'},
{'date': '201510171311', 'test': 'Test b', 'value': 45, 'range': ('10', '30')},
]
Example of data structure used in the app:
collated_table_data = [
OrderedDict([
('TEST', 'Test A'),
('RANGE', '<=10'),
('Feb. 16, 2014', 3),
('Oct. 17, 2014', 8),
('Oct. 17, 2015', None),
('Oct. 31, 2016', 6),
('Feb. 6, 2017', None)
]),
OrderedDict([
('TEST', 'Test b'),
('RANGE', '10 - 30'),
('Feb. 16, 2014', None),
('Oct. 17, 2014', 30),
('Oct. 17, 2015', 45),
('Oct. 31, 2016', 20),
('Feb. 6, 2017', 15)])
]
My process seems convoluted and confusing. I had a hard time figuring out what I did after a few months away. I don't like how I manage dates. It seems like I could sort by int('201410171311')
but that seemed to have some other consequences.
I'm not crazy about the efficiency of the code. I need to continually collate the data as it gets updated, so it gets called frequently. But speed hasn't been an issue so far.
Rows and columns need to be sorted by name and date.
from collections import OrderedDict
from datetime import datetime
from tabulate import tabulate
def dateobj(s):
"""
Returns a date object from a string format of '201702270153'
"""
return datetime(int(s[:4]), int(s[4:6]), int(s[6:8]))
def datestr(dt):
"""
Returns a readable date string from a datetime object.
"""
return dt.strftime('%b. %-d, %Y')
def collate_results(data):
# Create datetime objects for easier sorting.
dates = sorted(set([dateobj(t['date']) for t in data]))
# Convert to readable format.
dates = [datestr(d) for d in dates]
column_names = ['TEST', 'RANGE'] + dates
test_names = set([t['test'] for t in data])
# Case insensitive sort. Test names may contain non alpha chars.
test_names = sorted(list(test_names),
key=lambda s: ''.join(ch.lower() for ch in s if ch.isalpha()))
rows = []
for name in test_names:
name_tests = [t for t in data if t['test'] == name]
row = OrderedDict.fromkeys(column_names, None)
for item in name_tests:
row['TEST'] = item['test']
if type(item['range']) is tuple:
row['RANGE'] = item['range'][0] + ' - ' + item['range'][1]
elif type(item['range']) is str:
row['RANGE'] = item['range']
else:
row['RANGE'] = None
# item['date'] is in a string like, '201410171311'
# So, it needs to be converted and associated with the correct column.
row[datestr(dateobj(item['date']))] = item['value']
rows.append(row)
return rows
if __name__ == '__main__':
data = [
{'date': '201410171311', 'test': 'Test b', 'value': 30, 'range': ('10', '30')},
{'date': '201610310152', 'test': 'Test A', 'value': 6, 'range': '<=10'},
{'date': '201410171311', 'test': 'Test A', 'value': 8, 'range': '<=10'},
{'date': '201702062358', 'test': 'Test b', 'value': 15, 'range': ('10', '30')},
{'date': '201610310152', 'test': 'Test b', 'value': 20, 'range': ('10', '30')},
{'date': '201402162358', 'test': 'Test A', 'value': 3, 'range': '<=10'},
{'date': '201510171311', 'test': 'Test b', 'value': 45, 'range': ('10', '30')},
]
collated_table_data = [
OrderedDict([
('TEST', 'Test A'),
('RANGE', '<=10'),
('Feb. 16, 2014', 3),
('Oct. 17, 2014', 8),
('Oct. 17, 2015', None),
('Oct. 31, 2016', 6),
('Feb. 6, 2017', None)
]),
OrderedDict([
('TEST', 'Test b'),
('RANGE', '10 - 30'),
('Feb. 16, 2014', None),
('Oct. 17, 2014', 30),
('Oct. 17, 2015', 45),
('Oct. 31, 2016', 20),
('Feb. 6, 2017', 15)])
]
table = collate_results(data)
print table == expected
print tabulate(table, headers="keys")
Output:
True
TEST RANGE Feb. 16, 2014 Oct. 17, 2014 Oct. 17, 2015 Oct. 31, 2016 Feb. 6, 2017
------ ------- --------------- --------------- --------------- --------------- --------------
Test A <=10 3 8 6
Test b 10 - 30 30 45 20 15
table == expected
part, should we readtable == collated_table_data
instead? \$\endgroup\$