# -*- 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'))