1#!/usr/bin/env python3
2
3"""Web page to show algorithm constants."""
4
5import os
6import ast
7import logging
8import operator
9import runpy
10import collections
11
12import numpy as np
13
14# import before Django modules
15from chart.project import settings
16
17from django.shortcuts import render
18
19from chart.common.prettyprint import Table
20from chart.common.prettyprint import show_time
21from chart.backend.activity import Activity
22from chart.common.decorators import memoized
23from chart.browse.make_url import make_url
24
25
26def islist(obj):
27 """True if `obj` is a list-type object."""
28 return isinstance(obj, (list, tuple, np.ndarray))
29
30
31EXP_BIG_THRESHOLD = 1000
32EXP_SMALL_THRESHOLD = 0.0001
33
34
35def constants_in_file(filename):
36 """Given a Python file name yield names of all constants used."""
37 tree = ast.parse(filename.open('r').read())
38 for elem in ast.walk(tree):
39 if (isinstance(elem, ast.Call) and
40 isinstance(elem.func, ast.Name) and
41 elem.func.id == 'get_constant'):
42
43 constant = None
44
45 for e in elem.args:
46 try:
47 constant = e.s
48 except AttributeError:
49 # this can happen when calling get_constant with positional attributes
50 logging.error('Error parsing get_constants call in {filename}'.format(
51 filename=filename))
52 continue
53
54 for e in elem.keywords:
55 if e.arg == 'name':
56 constant = e.value.s
57
58 if constant is None:
59 logging.error('Could not decode get_constant call in ' + filename)
60
61 else:
62 yield constant
63
64
65@memoized
66def usage():
67 """For each constant, identify where it is used."""
68 res = collections.defaultdict(list)
69 for activity in Activity.all():
70 if activity.executable is not None and activity.executable.suffix == '.py':
71 # the html template allows each executable to have multiple activities
72 # pointing to it
73 for constant in constants_in_file(activity.abs_executable):
74 res[constant].append({'exe': activity.executable.name,
75 'activities': [activity]})
76
77 return res
78
79
80def makehtml(obj, constant):
81 """Convert a single constant into the inside of an html table cell.
82 This is a recursive call since a constant may be a complex object.
83 """
84 # scalar
85 if not islist(obj):
86 # print 'obj ', obj, ' type ', type(obj)
87 if isinstance(obj, int) or isinstance(obj, np.number):
88 if np.abs(obj) > EXP_BIG_THRESHOLD or np.abs(obj) < EXP_SMALL_THRESHOLD:
89 return '{0:e}'.format(obj)
90
91 elif isinstance(obj, dict):
92 return ''.join('<p>{k}: {v}</p>'.format(k=k, v=v) for k, v in obj.items())
93
94 return str(obj)
95
96 # empty list
97 if isinstance(obj, list) and len(obj) == 0:
98 t = Table()
99 if 'labels' in constant:
100 t.append_headers(constant['labels'])
101
102 t.append((makehtml(x, constant) for x in obj))
103 return t.to_html_str()
104
105 # 1d
106 if not islist(obj[0]):
107 t = Table()
108 if 'labels' in constant:
109 t.append_headers(constant['labels'])
110
111 t.append((makehtml(x, constant) for x in obj))
112 return t.to_html_str()
113
114 # 2d
115 if not islist(obj[0][0]):
116 t = Table()
117 row_labels = None
118 if 'labels' in constant:
119 if not islist(constant['labels']):
120 # labels might be a single array ...
121 t.append_headers(constant['labels'])
122
123 else:
124 # ... or an array of arrays, with the labels for each dimension in a separate list
125 if len(constant['labels']) > 1:
126 # leave space for left hand column
127 t.append_headers([''] + list(constant['labels'][0]))
128 row_labels = constant['labels'][1]
129
130 else:
131 t.append_headers(constant['labels'][0])
132
133 if row_labels:
134 for label, line in zip(row_labels, obj):
135 t.append([label + ':'] + [makehtml(x, constant) for x in line])
136
137 else:
138 for line in obj:
139 t.append((makehtml(x, constant) for x in line))
140
141 return t.to_html_str()
142
143 # more dimensions
144 return '<br>'.join(makehtml(x, constant) for x in obj)
145
146
147def constants(request):
148 """Compile a table of all constants from `chart.alg.constants`."""
149
150 users = usage()
151 res = []
152 # background_colours = ('#EEEEEE', '#DDDDFF')
153 # background_index = [0]
154 # prev_users = [None]
155
156 # def background_colour(users):
157 # """Attempt to show alternating background colours, with constants for each algorithm
158 # shown in a different colour...
159 # This does not produce a particularly good effect.
160 # """
161
162 # if users != prev_users[0]:
163 # background_index[0] = (background_index[0] + 1) % len(background_colours)
164
165 # prev_users[0] = users
166
167 # return background_colours[background_index[0]]
168
169 constants_file = settings.CONSTANTS_FILE
170
171 if constants_file is None:
172 return render(request,
173 'alg/constants.html',
174 dict(constants=res,
175 url=None))
176
177 all_constants = runpy.run_path(settings.CONSTANTS_FILE)['data']
178
179 for k, v in all_constants.items():
180 unit = v.get('unit', '')
181 description = v.get('description', '')
182
183 if 'value' in v:
184 res.append({'name': k,
185 'description': description,
186 'unit': unit,
187 'validity': '',
188 'value': makehtml(v['value'], v),
189 'users': users[k]})
190
191 else:
192 for value in v['values']:
193 # for each possible value of the constant (each one specified with
194 # scid and/or start validity time) append a row to the HTML output
195 if 'sensing_start' in value:
196 validity = '{scid} / {time}'.format(scid=value.get('scid', ''),
197 time=show_time(value['sensing_start']))
198
199 else:
200 validity = value.get('scid', '')
201
202 res.append({'name': k,
203 'description': description,
204 'unit': unit,
205 'validity': validity,
206 'value': makehtml(value['value'], v),
207 'users': users[k]})
208
209 res.sort(key=operator.itemgetter('name', 'validity'))
210
211 for r in res:
212 r['background'] = '#eeeeee'
213 # r['background'] = background_colour(r['users'])
214
215 # make browseable link to constants module file. Should probably patch make_url
216 # to accept modules instead
217 # rel = str(settings.CONSTANTS_FILE).replace('.', '/') + '.py'
218 url = make_url(settings.CONSTANTS_FILE)
219 return render(request,
220 'alg/constants.html',
221 dict(constants=res,
222 url=url))
223
224
225def main():
226 """Dump representation of a single constant."""
227 # print makehtml(data['MHS.central_frequency'][0]['value'])
228
229 print(usage())
230
231 def dump(elem):
232 """Display a single constant."""
233 return ', '.join('{name}={value}'.format(
234 name=name,
235 value=elem.__getattribute__(name)) for name in elem._fields)
236
237 def debug(): # (unused variable) pylint: disable=W0612
238 """Scan the AST of a module and print sections relating to a call
239 to `get_constant`."""
240
241 tree = ast.parse(open(os.path.join(settings.ALGORITHM_DIR, 'mhs_nedt.py'), 'r').read())
242 for elem in ast.walk(tree):
243 # if 'name' in elem._fields:
244 # print elem.name, dump(elem)
245 # if type(elem) == ast.Name:
246 # print elem.id
247 if isinstance(elem, ast.Call):
248 if isinstance(elem.func, ast.Attribute):
249 if 'id' in elem.func.value._fields:
250 print('CALL ATTRIBUTE ' + elem.func.value.id + ' . ' + elem.func.attr)
251 else:
252 if 'value' in elem.func.value._fields:
253 if 'id' in elem.func.value.value._fields:
254 print('CALL ATTRIBUTE ' + elem.func.value.value.id + ' [] ' +
255 elem.func.attr)
256 else:
257 print('UNKNOWN2')
258
259 elif 's' in elem.func.value._fields:
260 print('MSG ' + elem.func.value.s)
261 else:
262 print('UNKNOWN')
263
264 elif isinstance(elem.func, ast.Name):
265 print('CALL NAME ' + elem.func.id)
266 if elem.func.id == 'get_constant':
267 print(elem.func, elem.args, elem.keywords, elem.starargs, elem.kwargs)
268 for e in elem.args:
269 print('GET_CONSTANT , ' + e.s)
270
271 for e in elem.keywords:
272 print('keyword ' + dump(e))
273 if e.arg == 'name':
274 print('GET_CONSTANT = ' + e.value.s)
275 # print len(elem.args)
276 # print len(elem.keywords)
277 # print elem.func.ctx
278 # print dump(elem.func.ctx)
279
280if __name__ == '__main__':
281 main()