From 20e9beaf693a9407ff452c00e6ec7b58ba6898bd Mon Sep 17 00:00:00 2001 From: anwinged Date: Tue, 24 Apr 2012 09:05:52 +0000 Subject: [PATCH] Code refactoring --- forms.py | 74 +++++++++++--- opal.py | 247 +++++++++++++++++++++++++++++++++-------------- server.py | 12 ++- task.py | 21 ++-- tasks/testt.json | 6 +- tasks/testt.py | 70 ++++++++++---- 6 files changed, 313 insertions(+), 117 deletions(-) diff --git a/forms.py b/forms.py index 919ae69..77d1663 100644 --- a/forms.py +++ b/forms.py @@ -2,18 +2,40 @@ import wx import wx.gizmos +import wx.grid import wx.propgrid as wxpg +import wx.lib.plot as wxplot ID_TEST = wx.NewId() ID_ADD_MODEL_ROOT = wx.NewId() ID_ADD_MODEL_SELECTED = wx.NewId() ID_DUPLICATE_MODEL = wx.NewId() +ID_DUPLICATE_TREE = wx.NewId() ID_DELETE_MODEL = wx.NewId() ID_PROCESS_MODEL = wx.NewId() +ID_SHOW_RESULT = wx.NewId() +ID_SHOW_PLOT = wx.NewId() + +class TreeListCtrl(wx.gizmos.TreeListCtrl): + + def __iter__(self): + return TreeListCtrlIterator(self) + +class TreeListCtrlIterator: + def __init__(self, owner): + self.owner = owner + self.item = self.owner.GetRootItem() + + def __iter__(self): + return self + + def next(self): + if not self.item.IsOk(): + raise StopIteration + item = self.item + self.item = self.owner.GetNext(self.item) + return item -class MyTreeListCtrl(wx.gizmos.TreeListCtrl): - def Refresh(self, erase, rect): - wx.gizmos.TreeListCtrl.Refresh(False, rect) class MainFrame (wx.Frame): @@ -31,9 +53,7 @@ class MainFrame (wx.Frame): bSizer4 = wx.BoxSizer(wx.VERTICAL) - self.m_user_models = wx.gizmos.TreeListCtrl(self, - #self.m_user_models = MyTreeListCtrl(self, - #self.m_user_models = wx.TreeCtrl(self, + self.m_user_models = TreeListCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS | wx.TR_ROW_LINES | wx.TR_MULTIPLE) self.m_user_models.SetMinSize(wx.Size(-1, 200)) @@ -45,8 +65,7 @@ class MainFrame (wx.Frame): bSizer4.Add(self.m_user_models, 0, wx.ALL|wx.EXPAND, 1) # WARNING: wxPython code generation isn't supported for this widget yet. - self.m_params = wxpg.PropertyGridManager(self, - style = wxpg.PG_TOOLBAR) + self.m_params = wxpg.PropertyGridManager(self) self.m_params.AddPage('fp') bSizer4.Add(self.m_params, 1, wx.EXPAND |wx.ALL, 1) @@ -80,14 +99,18 @@ class MainFrame (wx.Frame): menubar.Append(menu, '&File') menu = wx.Menu() - menu.Append(ID_TEST, "&Test\tCtrl+T") + menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R') + menu.Append(ID_SHOW_RESULT, 'Show result\tCtrl+S') + menu.Append(ID_SHOW_PLOT, 'Show plot\tCtrl+G') + menu.AppendSeparator() menu.Append(ID_ADD_MODEL_ROOT, 'Add model to root') menu.Append(ID_ADD_MODEL_SELECTED, 'Append model to selected') - #menu.AppendSeparator() - menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D") - menu.Append(ID_DELETE_MODEL, 'Delete') menu.AppendSeparator() - menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R') + menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D") + menu.Append(ID_DUPLICATE_TREE, "&Duplicate with subitems\tCtrl+Shift+D") + menu.Append(ID_DELETE_MODEL, 'Delete\tCtrl+E') + menu.AppendSeparator() + menu.Append(ID_TEST, "&Test\tCtrl+T") menubar.Append(menu, '&Model') menu = wx.Menu() @@ -104,3 +127,28 @@ class MainFrame (wx.Frame): menu.Append(ID_ADD_MODEL_SELECTED, 'Add model to selected') self.m_specs.Bind(wx.EVT_CONTEXT_MENU, lambda x: self.m_specs.PopupMenu(menu)) + +class ResultFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400)) + + sizer = wx.BoxSizer(wx.VERTICAL) + + self.scalar = wxpg.PropertyGridManager(self) + self.scalar.AddPage('fp') + + self.table = wx.grid.Grid(self) + self.table.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER) + + sizer.Add(self.scalar, 0, wx.EXPAND |wx.ALL, 1) + sizer.Add(self.table, 1, wx.EXPAND |wx.ALL, 1) + + self.SetSizer(sizer) + self.Layout() + self.Centre(wx.BOTH) + +class PlotFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400)) + + self.plot = wxplot.PlotCanvas(self) \ No newline at end of file diff --git a/opal.py b/opal.py index bd458e6..aa2c764 100644 --- a/opal.py +++ b/opal.py @@ -22,6 +22,25 @@ import os import threading import re +class ModelData: + def __init__(self, server, model, parent_data = None): + # если мы создаем новый набор данных из описания модели + if isinstance(model, task.DataDescription): + self.mdef = task.DataDefinition(model, parent_data) + self.jid = server.CreateJob() if model.IsExecutable() else None + # если мы создаем набор данных из другого набора данных + elif isinstance(model, ModelData): + self.mdef = model.mdef.Copy() + self.jid = server.CreateJob() if model.jid != None else None + else: + self.mdef = None + self.jid = None + + self.res = None + +class ItemError(Exception): + pass + #----------------------------------------------------------------------------- # Главная форма #----------------------------------------------------------------------------- @@ -47,21 +66,28 @@ class MainFrame(forms.MainFrame): self.m_params.Bind(wxpg.EVT_PG_CHANGED, self.OnParamChanged) - self.Bind(wx.EVT_MENU, self.OnTest, id = forms.ID_TEST) + self.Bind(wx.EVT_MENU, self.OnTest, + id = forms.ID_TEST) self.Bind(wx.EVT_MENU, self.OnAddModelToRoot, id = forms.ID_ADD_MODEL_ROOT) self.Bind(wx.EVT_MENU, self.OnAddModelToSelected, id = forms.ID_ADD_MODEL_SELECTED) self.Bind(wx.EVT_MENU, self.OnDuplicate, id = forms.ID_DUPLICATE_MODEL) + self.Bind(wx.EVT_MENU, self.OnDuplicateTree, + id = forms.ID_DUPLICATE_TREE) + self.Bind(wx.EVT_MENU, self.OnDeleteModel, + id = forms.ID_DELETE_MODEL) self.Bind(wx.EVT_MENU, self.OnModelProcess, id = forms.ID_PROCESS_MODEL) + self.Bind(wx.EVT_MENU, self.OnShowResult, + id = forms.ID_SHOW_RESULT) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_IDLE, self.OnIdle) ov = threading.Thread(target = self.Overseer) - ov.daemon = 1 + ov.daemon = True ov.start() self.NewProject(model) @@ -72,6 +98,12 @@ class MainFrame(forms.MainFrame): self.Destroy() def Overseer(self): + """ + Функция-надсмотрщик, которая периодически проверяет состояние + всех пользовательских моделей, в зависимости от этого изменяет + состояние окружения, выводит информацию, подгружает результаты + выполнения работ и др. + """ def StateToStr(state): if state == server.JOB_READY: @@ -90,25 +122,26 @@ class MainFrame(forms.MainFrame): cycle_count = 0 while True: wx.MutexGuiEnter() - print 'cycle{:-8}'.format(cycle_count) + #print 'cycle{:-8}'.format(cycle_count) cycle_count += 1 - item = um.GetRootItem() - while item.IsOk(): + # просматриваем всю иерархию моделей + for item in um: data = um.GetPyData(item) - if data: - jid = data[1] - if jid != None and self.server.IsJobChanged(jid): - state = self.server.GetJobState(jid) - um.SetItemText(item, StateToStr(state[0]), 1) - p = state[1] - p = 'Unknown' if p < 0 else '{:%}'.format(p) - um.SetItemText(item, p, 2) - um.SetItemText(item, state[2], 3) - print jid, state + if not data: + continue + jid = data.jid + if jid != None and self.server.IsJobChanged(jid): + state, percent, comment = self.server.GetJobState(jid) + um.SetItemText(item, StateToStr(state), 1) + p = 'Unknown' if percent < 0 else '{:%}'.format(percent) + um.SetItemText(item, p, 2) + um.SetItemText(item, comment, 3) + print jid, (state, percent, comment) + if state == server.JOB_COMPLETED: + data.res = self.server.GetJobResult(jid) - item = um.GetNext(item) wx.MutexGuiLeave() - time.sleep(0.2) + time.sleep(0.1) except Exception, e: print 'Error in overseer: ', e @@ -118,12 +151,10 @@ class MainFrame(forms.MainFrame): Возвращает True, если имя уникально, иначе False. """ um = self.m_user_models - item = um.GetRootItem() - while item.IsOk(): + for item in um: item_name = um.GetItemText(item) if item_name == name: return False - item = um.GetNext(item) return True def GenerateName(self, name): @@ -131,7 +162,7 @@ class MainFrame(forms.MainFrame): На основе переданного имени генерирует новое имя модели таким образом, чтобы оно осталось уникальным в рамках существующей иерархии моделей. """ - m = re.match(r'(.+)\s+\d*', name) + m = re.match(r'^(.+)\s+\d*$', name, re.UNICODE) basename = m.group(1) if m else name while True: name = basename + ' ' + str(self.name_id) @@ -172,13 +203,17 @@ class MainFrame(forms.MainFrame): um = self.m_user_models item = um.GetRootItem() defparent = None - for m in ms: + root = None + for i, m in enumerate(ms): name = self.GenerateName(m.GetTitle()) item = um.AppendItem(item, name) - data = task.DataDefinition(m, defparent) - defparent = data - jid = self.server.CreateJob() if m.IsExecutable() else None - um.SetPyData(item, [data, jid]) + if not i: + root = item + data = ModelData(self.server, m, defparent) + defparent = data.mdef + um.SetPyData(item, data) + if root: + um.Expand(root) def NewProject(self, model): # 1. загрузить спецификации модели @@ -194,7 +229,7 @@ class MainFrame(forms.MainFrame): return True # Project(model) - def SelectUserModel(self, model_def, jid): + def SelectUserModel(self, model_def): def SelectProperty(param_type): """ @@ -220,23 +255,42 @@ class MainFrame(forms.MainFrame): msg = model_def.PackParams() pg = self.m_params pg.ClearPage(0) - #pg.Append(wxpg.PropertyCategory('Model properties')) - for k, v in model_def.params.iteritems(): - p = model_def.DD[k] - title = p.GetTitle() or k - prop = SelectProperty(p.GetType()) - pid = pg.Append(prop(title, value = v)) - pg.SetPropertyClientData(pid, k) - pg.SetPropertyHelpString(pid, p.GetComment()) + for label, value in model_def.params.iteritems(): + param = model_def.DD[label] + title = param.GetTitle() or label + prop = SelectProperty(param.GetType()) + pid = pg.Append(prop(title, value = value)) + pg.SetPropertyClientData(pid, label) + pg.SetPropertyHelpString(pid, param.GetComment()) + + self.SetStatusText(model_def.PackParams(), 0) + + def GetSelectedItem(self, source): + item = source.GetSelection() + if not item.IsOk(): + raise ItemError('Invalid item') + return item + + def GetSelectedData(self, source): + item = self.GetSelectedItem(source) + data = source.GetPyData(item) + if not data: + raise ItemError('Empty data') + return data + + def GetSelectedItemData(self, source): + item = self.GetSelectedItem(source) + data = source.GetPyData(item) + if not data: + raise ItemError('Empty data') + return (item, data) - pd = model_def.PackParams() - self.SetStatusText(pd, 0) def OnModelActivated(self, event): item = event.GetItem() data = self.m_user_models.GetPyData(item) if data: - self.SelectUserModel(data[0], data[1]) + self.SelectUserModel(data.mdef) def OnParamChanging(self, event): #value = event.GetValue() @@ -251,69 +305,112 @@ class MainFrame(forms.MainFrame): return value = prop.GetValue() param = prop.GetClientData() - um = self.m_user_models - id = um.GetSelection() - data, jid = um.GetItemPyData(id) - data[param] = value + data = self.GetSelectedData(self.m_user_models) + data.mdef[param] = value def OnTest(self, event): um = self.m_user_models def OnAddModelToRoot(self, event): - item = self.m_specs.GetSelection() - if not item.IsOk(): - return - print self.m_specs.GetItemText(item) - model = self.m_specs.GetPyData(item) + model = self.GetSelectedData(self.m_specs) self.AddModelToRoot(model) def OnAddModelToSelected(self, event): """ - Добавляет пользовательскую спецификацию к указанной модели или в уже - существующую иерархию спецификаций. + Добавляет пользовательскую спецификацию к указанной модели """ - item = self.m_specs.GetSelection() - if not item.IsOk(): - return - model = self.m_specs.GetPyData(item) - + # получаем модель, которая будет добавлена к пользовательским + model = self.GetSelectedData(self.m_specs) + # получаем пользовательскую модель, к которой хотим присоединить новую + item, data = self.GetSelectedItemData(self.m_user_models) + pmdef = data.mdef um = self.m_user_models - item = um.GetSelection() - if not item.IsOk(): - return - - pmdef, _ = um.GetPyData(item) - + # если новая модель может быть присоединена... if pmdef.DD == model.parent: - modeldef = task.DataDefinition(model, pmdef) - name = self.GenerateName(model.GetTitle()) - item = um.AppendItem(item, name) - jid = self.server.CreateJob() if model.IsExecutable() else None - um.SetPyData(item, [modeldef, jid]) + name = self.GenerateName(model.GetTitle()) + child = um.AppendItem(item, name) + new_data = ModelData(self.server, model, pmdef) + um.SetPyData(child, new_data) + um.Expand(item) else: wx.MessageBox('It\'s impossible to append model', 'Error') def OnDuplicate(self, event): - um = self.m_user_models - id = um.GetSelection() - title = um.GetItemText(id) - parent = um.GetItemParent(id) - md, jid = um.GetItemPyData(id) + """ + Обработчик события "дублирование модели" - child = um.AppendItem(parent, self.GenerateName(title)) - jid = self.server.CreateJob() - um.SetPyData(child, [md.Copy(), jid]) + Когда модель дублируется, ее параметры копируются в новую модель, + при неоходимости выделяется слот для работ на сервере. + Результаты модели-оригинала не копируются. + """ + um = self.m_user_models + item, data = self.GetSelectedItemData(self.m_user_models) + title = um.GetItemText(item) + parent = um.GetItemParent(item) + child = um.AppendItem(parent, self.GenerateName(title)) + new_data = ModelData(self.server, data) + um.SetPyData(child, new_data) self.SetStatusText('Copy for "{}" created'.format(title), 0) + def OnDuplicateTree(self, event): + pass + + def OnDeleteModel(self, event): + item, data = self.GetSelectedItemData(self.m_user_models) + self.server.DeleteJob(data.jid) + self.m_user_models.Delete(item) + def OnModelProcess(self, event): um = self.m_user_models for i in um.GetSelections(): - data, jid = um.GetItemPyData(i) - self.server.LaunchJob(jid, data) + data = um.GetItemPyData(i) + self.server.LaunchJob(data.jid, data.mdef) + + def OnShowResult(self, event): + item, data = self.GetSelectedItemData(self.m_user_models) + title = self.m_user_models.GetItemText(item) + title = 'Result for model "{}"'.format(title) + rframe = ResultFrame(self, title, data.res) + rframe.Show() def OnIdle(self, event): pass +#----------------------------------------------------------------------------- +# Форма с результатами выполнения работы +#----------------------------------------------------------------------------- + +class ResultFrame(forms.ResultFrame): + def __init__(self, parent, title, result): + forms.ResultFrame.__init__(self, parent, title) + self.result = result + self.UpdateResults() + + def UpdateResults(self): + self.scalar.ClearPage(0) + self.table.ClearGrid() + if self.result: + table = self.result.get('table', []) + if table and len(table): + cols = len(table[0]) + rows = len(table) - 1 + self.table.CreateGrid(rows, cols) + # + for i, col in enumerate(table[0]): + label = "{} ({})".format(col[0], col[1]) + self.table.SetColLabelValue(i, label) + # + for ri, row in enumerate(table[1:]): + for ci, value in enumerate(row): + self.table.SetCellValue(ri, ci, str(value)) + + self.table.AutoSize() + + data = self.result.get('data', {}) + pg = self.scalar + for label, value in data.iteritems(): + pid = pg.Append(wxpg.StringProperty(label, value = str(value))) + #----------------------------------------------------------------------------- # Приложение #----------------------------------------------------------------------------- diff --git a/server.py b/server.py index e0b892e..5ddd350 100644 --- a/server.py +++ b/server.py @@ -42,7 +42,7 @@ class LocalServer: self.workers = workers # количество потоков выполнения self.tasks_meta = {} # идентификаор задачи self.models = [] # список моделей - self.next_job_id = 0 # очередной идентификатор работы + self.next_job_id = 1 # очередной идентификатор работы self.jobs = {} # очередб работ self.log = None # self.running = False # @@ -168,6 +168,12 @@ class LocalServer: if job != None: job.Stop() + def DeleteJob(self, jid): + job = self.jobs.get(jid) + if job != None: + job.Stop() + del self.jobs[jid] + #-------------------------------------------------------------------------- def Start(self): @@ -343,13 +349,13 @@ def main(): md['d'] = 10 md['r'] = 3.14 - slots = [ s.CreateJob() for i in xrange(5) ] + slots = [ s.CreateJob() for i in xrange(1) ] for jid in slots: md['n'] = random.randint(20, 30) print jid, md['n'] s.LaunchJob(jid, md) - time.sleep(30) + time.sleep(5) for jid in slots: pprint(s.GetJobResult(jid)) diff --git a/task.py b/task.py index 0da6b33..e01244e 100644 --- a/task.py +++ b/task.py @@ -32,17 +32,12 @@ class Parameter: def GetDefault(self): return self.data.get('default') - def GetTestExpresion(self): + def GetTestExpression(self): return self.data.get('test') def Test(self, value): return True - #def __repr__(self): - # return "'{}'".format( - # self.GetType() - # ) - #------------------------------------------------------------------------------- class DataDescription: @@ -88,6 +83,9 @@ class DataDescription: def __getitem__(self, label): return self.pdata.get(label) + def type(self): + return 'data-def' + #------------------------------------------------------------------------------- class DataDefinition: @@ -124,3 +122,14 @@ class DataDefinition: package.reverse() return json.dumps(package) + def type(self): + return 'data-def' + +#------------------------------------------------------------------------------- + +class ResultData: + def __init__(self, data): + self.data = data + + def GetColumns(self): + pass \ No newline at end of file diff --git a/tasks/testt.json b/tasks/testt.json index d400240..4f6b333 100644 --- a/tasks/testt.json +++ b/tasks/testt.json @@ -7,7 +7,7 @@ "sintaylor": { - "title": "Simple model", + "title": "Sin Taylor", "author": "Anton Vakhrushev", "date": "2012-03-08", @@ -40,11 +40,11 @@ "spec": { "left": { - "title": "Left" + "title": "Left rectangles" }, "right": { - "title": "Right" + "title": "Right rectangles" }, "trapezium": { diff --git a/tasks/testt.py b/tasks/testt.py index cb3e609..6456567 100644 --- a/tasks/testt.py +++ b/tasks/testt.py @@ -39,13 +39,20 @@ def error(msg): "comment": msg }) -def result(r): +def result(s, t): return json.dumps({ "answer": "result", "result": { - "table": [[ {"x": "double"}, {"y": "double"} ]] + r - } - }) + "data": s, + "table": t + }}) + +def serie(n, d, h, l = 0): + for i in xrange(n): + y = sin_taylor(l, d) + yield (l, y) + l += h + time.sleep(0.01) def main(): @@ -62,26 +69,55 @@ def main(): textdata = raw_input() data = json.loads(textdata) - if not len(data) or data[-1]['label'] != 'sintaylor': - write(error('Unknown model')) - sys.exit(1) - params = data[0]['params'] - l = 0 # левая граница r = params['r'] # правая граница n = params['n'] # количество шагов d = params['d'] # количество членов в разложении Тейлора - h = float(r - l) / n # шаг сетки по х + h = r / n res = [] # таблица резултатов - while l <= r: - y = sin_taylor(l, d) - res.append([l, y]) - write(answer(l / r, data[-1]['label'])) - l += h - time.sleep(0.1) + label = data[-1]['label'] + sum = 0 + + if label == 'sintaylor': + for x, y in serie(n, d, h): + res.append([x, y]) + write(answer(x / r, label)) + write(result({}, + [[ ['x', 'double'], [ 'y', 'double' ] ]] + res)) + + elif label == 'left': + for x, y in serie(n-1, d, h): + s = y * h + res.append([x, y, s]) + write(answer(x / r, label)) + sum += s + write(result( + { 'sum': sum }, + [[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res)) + + elif label == 'right': + for x, y in serie(n-1, d, h, h): + s = y * h + res.append([x, y, s]) + write(answer(x / r, label)) + sum += s + write(result( + { 'sum': sum }, + [[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res)) + + elif label == 'trapezium': + prev = 0 + for x, y in serie(n + 1, d, h): + s = 0.5 * (y + prev) * h + res.append([x, y, s]) + write(answer(x / r, label)) + sum += s + prev = y + write(result( + { 'sum': sum }, + [[ ['x', 'double'], [ 'y', 'double' ], [ 's', 'double' ] ]] + res)) - write(result(res)) except Exception, e: write(error('Fatal error: ' + str(e)))