You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
9.8 KiB
321 lines
9.8 KiB
# -*- encoding: utf-8 -*- |
|
from __future__ import print_function |
|
from collections import OrderedDict |
|
import re |
|
|
|
|
|
# Unified collection point for all empirically-determined biophysical |
|
# values. Each value is a tuple (val, source). |
|
DATA = OrderedDict() |
|
|
|
|
|
def get(*args, **kwds): |
|
""" Get a single value from the database using the supplied arguments |
|
to query. |
|
|
|
Optionally, one keyword argument may be a list of values, in which case |
|
a dict will be returned containing {listval: dbval} pairs for each value in |
|
the list. |
|
""" |
|
return _lookup(0, *args, **kwds) |
|
|
|
|
|
def get_source(*args, **kwds): |
|
""" Get the source of a single value from the database using the supplied |
|
arguments to query. |
|
|
|
Optionally, one keyword argument may be a list of values, in which case |
|
a dict will be returned containing {listval: dbval} pairs for each value in |
|
the list. |
|
""" |
|
return _lookup(1, *args, **kwds) |
|
|
|
|
|
def print_table(table): |
|
for k in DATA.keys(): |
|
if table == k[0]: |
|
print("data key: ", k) |
|
print(DATA[k][0]) |
|
|
|
|
|
def get_table_info(table): |
|
""" |
|
Return a dictionary of row and column names in the table |
|
""" |
|
tinfo = {} |
|
for k in DATA.keys(): |
|
if table == k[0]: |
|
for p in k: |
|
if not isinstance(p, tuple): |
|
continue |
|
if p[0] not in tinfo.keys(): |
|
tinfo[p[0]] = [] |
|
if p[1] not in tinfo[p[0]]: |
|
tinfo[p[0]].append(p[1]) |
|
return tinfo |
|
|
|
|
|
def _lookup(ind, *args, **kwds): |
|
key = mk_key(*args, **kwds) |
|
if isinstance(key, dict): |
|
data = {} |
|
for k, key in key.items(): |
|
data[k] = DATA[key][ind] |
|
return data |
|
else: |
|
return DATA[key][ind] |
|
|
|
|
|
def setval(val, *args, **kwds): |
|
key = mk_key(*args, **kwds) |
|
oldval = None |
|
# change_flag = False |
|
if key in DATA: |
|
# change_flag = True # any attempt to change key will set this |
|
oldval = DATA[key] # save the previous stored value |
|
# raise RuntimeError("Data key '%s' has already been set." % str(key)) |
|
DATA[key] = val |
|
return oldval |
|
|
|
|
|
def mk_key(*args, **kwds): |
|
# Make a unique key (or list of keys) used to access values from the |
|
# database. The generated key is independent of the order that arguments |
|
# are specified. |
|
# |
|
# Optionally, one keyword argument may have a list of values, in which case |
|
# the function will return a dict containing {listval: key} pairs for each |
|
# value in the list. |
|
listkey = None |
|
for k, v in kwds.items(): |
|
if isinstance(v, (list, tuple)): |
|
if listkey is not None: |
|
raise TypeError("May only specify a list of values for one key.") |
|
listkey = k |
|
|
|
if listkey is None: |
|
return _mk_key(*args, **kwds) |
|
else: |
|
keys = {} |
|
for v in kwds[listkey]: |
|
kwds[listkey] = v |
|
keys[v] = _mk_key(*args, **kwds) |
|
return keys |
|
|
|
|
|
def _mk_key(*args, **kwds): |
|
key = list(args) + list(kwds.items()) |
|
key.sort(key=lambda a: a[0] if isinstance(a, tuple) else a) |
|
return tuple(key) |
|
|
|
|
|
def add_table_data(name, row_key, col_key, data, **kwds): |
|
""" |
|
Read data like:: |
|
|
|
Description |
|
|
|
------------------------------------ |
|
col1 col2 col3 |
|
row1 1.2 [1] 0.9e-6 [1] 27 [2] |
|
row2 1.7 [1] [3] |
|
row3 0.93 [2] 0.3e-6 3 [2] |
|
|
|
------------------------------------ |
|
|
|
[1] citation 1 |
|
[2] citation 2 |
|
[3] missing because. |
|
|
|
|
|
""" |
|
if isinstance(data, str) and "\xc2" in data: |
|
raise TypeError( |
|
"Data table <%s> appears to contain unicode characters but" |
|
"was not defined as unicode." % name |
|
) |
|
|
|
lines = data.split("\n") |
|
|
|
# First, split into description, table, and sources using ----- lines |
|
desc = [] |
|
table = [] |
|
while lines: |
|
line = lines.pop(0) |
|
# print ">", line |
|
if re.match(r"\s*-+\s*$", line): |
|
# print "match!" |
|
break |
|
desc.append(line) |
|
while lines: |
|
line = lines.pop(0) |
|
# print ">", line |
|
if re.match(r"\s*-+\s*$", line): |
|
# print "match!" |
|
break |
|
table.append(line) |
|
|
|
# print desc |
|
# print table |
|
|
|
# parse remaining lines as sources |
|
sources = parse_sources(lines) |
|
# print sources |
|
|
|
# |
|
# parse table |
|
# table might be empty, so take care of that first. |
|
if table == []: |
|
return [] # no changes |
|
|
|
while len(table[0].strip()) == 0: |
|
table.pop(0) |
|
|
|
spaces = [c == " " for c in table[0]] |
|
cols = [0] + [i for i in range(1, len(spaces)) if spaces[i - 1] and not spaces[i]] |
|
cols = cols + [max(map(len, table)) + 1] |
|
# print spaces |
|
# print cols |
|
# Make sure columns are obeyed strictly |
|
for i, line in enumerate(table): |
|
for j, c in enumerate(cols[1:]): |
|
if len(line) < c: |
|
continue |
|
if line[c - 1] != " ": |
|
print("Table line with error: \n ", line) |
|
raise Exception( |
|
"Table <%s> line: %d, column: %s does not obey column boundaries." |
|
% (name, i, j) |
|
) |
|
|
|
# Break table into cells |
|
cells = [] |
|
for line in table: |
|
if line.strip() != "": |
|
cells.append( |
|
[line[cols[i] : cols[i + 1]].strip() for i in range(len(cols) - 1)] |
|
) |
|
# print cells |
|
|
|
# Extract row/column names |
|
col_names = cells.pop(0)[1:] |
|
row_names = [cells[i].pop(0) for i in range(len(cells))] |
|
if len(set(row_names)) != len(row_names): |
|
for n in set(row_names): |
|
row_names.remove(n) |
|
raise NameError("Duplicate row names: %s" % row_names) |
|
|
|
# Parse cell values |
|
for i in range(len(cells)): |
|
for j in range(len(cells[0])): |
|
cell = cells[i][j].strip() |
|
m = re.match(r"([^\[]*)(\[([^\]]+)\])?", cell) # match like "0.7 [3]" |
|
if m is None: |
|
raise ValueError( |
|
"Table cell (%d, %d) has bad format: '%s'" % (i, j, cell) |
|
) |
|
|
|
# parse value |
|
# If the value contains '±' then a tuple is returned containing the values |
|
# on either side. |
|
val, _, source = m.groups() |
|
# val = unicode(val) # python 2 |
|
val = str(val) # python 3 |
|
if val.strip() == "": |
|
val = None |
|
else: |
|
parts = val.split(u"±") |
|
vals = [] |
|
for p in parts: |
|
try: |
|
p = int(p) |
|
except ValueError: |
|
try: |
|
p = float(p) |
|
except ValueError: |
|
try: |
|
p = str( |
|
p.strip() |
|
) # allow strings to identify mechanisms also |
|
except ValueError: |
|
raise ValueError( |
|
"Table cell (%d, %d) value has bad format: '%s'" |
|
% (i, j, val) |
|
) |
|
vals.append(p) |
|
if len(vals) == 1: |
|
val = vals[0] |
|
else: |
|
val = tuple(vals) |
|
|
|
# parse source |
|
if source is not None: |
|
try: |
|
source = sources[source] |
|
except KeyError: |
|
raise ValueError( |
|
"Table cell (%d, %d) has unknown source key: '%s'" |
|
% (i, j, source) |
|
) |
|
|
|
cells[i][j] = (val, source) |
|
|
|
changes = [] # a list of parameters that are changed if we are rewriting a table |
|
for i, row in enumerate(row_names): |
|
for j, col in enumerate(col_names): |
|
kwds[row_key] = row |
|
kwds[col_key] = col |
|
oldval = setval(cells[i][j], name, **kwds) |
|
if oldval is not None and oldval != cells[i][j]: |
|
key = mk_key(name, **kwds) |
|
changes.append( |
|
{"key": key, "new": cells[i][j], "old": oldval, "name": name} |
|
) |
|
# changes.append({'name': name, 'row': row, 'col': col, 'new': cells[i][j], 'old': oldval}) |
|
return changes |
|
|
|
|
|
def report_changes(changes): |
|
""" |
|
For changes to data tables, give user a readout |
|
""" |
|
if len(changes) > 0: |
|
anychg = False |
|
for ch in changes: |
|
# print(' >>> Changing %s, %s from default (%s) to %s' % (ch['row'], ch['col'], str(ch['new'][0]), str(ch['old'][0]))) |
|
if str(ch["old"][0]) != str(ch["new"][0]): |
|
if anychg is False: |
|
print( |
|
"\nWarning: Data Table '%s' (in memory) has been modified!" |
|
% changes[0]["name"] |
|
) |
|
anychg = True |
|
print( |
|
" >>> Changing %s, from default (%s) to %s" |
|
% (ch["key"], str(ch["old"][0]), str(ch["new"][0])) |
|
) |
|
|
|
|
|
def parse_sources(lines): |
|
sources = {} |
|
key = None |
|
val = [] |
|
for l in lines: |
|
l = l.lstrip() |
|
m = re.match(r"\s*\[([^\]]+)\]\s+(.*)$", l) |
|
if m is not None: |
|
key = m.groups()[0] |
|
sources[key] = m.groups()[1].strip() |
|
else: |
|
if key is None: |
|
if l == "": |
|
continue |
|
raise ValueError( |
|
"Incorrect sources format--got text without " |
|
'citation index: "%s".' % l |
|
) |
|
sources[key] += "\n" + l |
|
return sources |
|
|
|
|
|
# parse_sources('''\n\n[1] source 1\n it's cool.\n[2] source 2 is not\n'''.split('\n'))
|
|
|