From 65aa81844ba53ba5fcb0f6791d3f6575115b49af Mon Sep 17 00:00:00 2001 From: Yingtong Li Date: Thu, 11 Jan 2018 19:06:21 +0800 Subject: [PATCH] Implement enums --- eos/base/tests.py | 12 ++-- eos/base/workflow.py | 38 ++++++------ eos/core/objects/__init__.py | 58 +++++++++++++++++++ eos/core/tasks/__init__.py | 31 +++++----- eos/core/tasks/direct.py | 6 +- eos/core/tests.py | 6 +- eos/psr/tests.py | 10 ++-- eosweb/core/main.py | 10 ++-- eosweb/core/templates/base.html | 4 +- .../core/templates/election/admin/admin.html | 4 +- eosweb/core/templates/election/view/view.html | 4 +- .../{ => task}/active_tasks_menu.html | 4 +- 12 files changed, 124 insertions(+), 63 deletions(-) rename eosweb/core/templates/{ => task}/active_tasks_menu.html (93%) diff --git a/eos/base/tests.py b/eos/base/tests.py index 6be2bb4..b4980d0 100644 --- a/eos/base/tests.py +++ b/eos/base/tests.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -26,13 +26,13 @@ class ElectionTestCase(EosTestCase): cls.db_connect_and_reset() def do_task_assert(self, election, task, next_task): - self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.READY) + self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.READY) if next_task is not None: - self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.NOT_READY) + self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.NOT_READY) election.workflow.get_task(task).enter() - self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.EXITED) + self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.EXITED) if next_task is not None: - self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.READY) + self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.READY) @py_only def test_run_election(self): @@ -44,7 +44,7 @@ class ElectionTestCase(EosTestCase): self.assertEqual(election.workflow._instance, (election, 'workflow')) # Check workflow behaviour - self.assertEqual(election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status, WorkflowTask.Status.READY) + self.assertEqual(election.workflow.get_task('eos.base.workflow.TaskConfigureElection').status, WorkflowTaskStatus.READY) self.assertEqual(election.workflow.get_task('does.not.exist'), None) # Set election details diff --git a/eos/base/workflow.py b/eos/base/workflow.py index 7ddd10e..f5987ac 100644 --- a/eos/base/workflow.py +++ b/eos/base/workflow.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -17,19 +17,19 @@ from eos.core.objects import * from eos.core.tasks import * +class WorkflowTaskStatus(EosEnum): + UNKNOWN = 0 + NOT_READY = 10 + READY = 20 + ENTERED = 30 + #COMPLETE = 40 + EXITED = 50 + class WorkflowTask(EmbeddedObject): - class Status: - UNKNOWN = 0 - NOT_READY = 10 - READY = 20 - ENTERED = 30 - #COMPLETE = 40 - EXITED = 50 - depends_on = [] provides = [] - status = IntField(default=0, is_hashed=False) + status = EnumField(WorkflowTaskStatus, is_hashed=False) exited_at = DateTimeField(is_hashed=False) def __init__(self, *args, **kwargs): @@ -40,8 +40,8 @@ class WorkflowTask(EmbeddedObject): self.workflow = self.recurse_parents(Workflow) - if self.status == WorkflowTask.Status.UNKNOWN: - self.status = WorkflowTask.Status.READY if self.are_dependencies_met() else WorkflowTask.Status.NOT_READY + if self.status == WorkflowTaskStatus.UNKNOWN: + self.status = WorkflowTaskStatus.READY if self.are_dependencies_met() else WorkflowTaskStatus.NOT_READY self.listeners = { 'enter': [], @@ -51,7 +51,7 @@ class WorkflowTask(EmbeddedObject): # Helpers def on_dependency_exit(): - self.status = WorkflowTask.Status.READY if self.are_dependencies_met() else WorkflowTask.Status.NOT_READY + self.status = WorkflowTaskStatus.READY if self.are_dependencies_met() else WorkflowTaskStatus.NOT_READY for depends_on_desc in self.depends_on: for depends_on_task in self.workflow.get_tasks(depends_on_desc): depends_on_task.listeners['exit'].append(on_dependency_exit) @@ -59,7 +59,7 @@ class WorkflowTask(EmbeddedObject): def are_dependencies_met(self): for depends_on_desc in self.depends_on: for depends_on_task in self.workflow.get_tasks(depends_on_desc): - if depends_on_task.status is not WorkflowTask.Status.EXITED: + if depends_on_task.status is not WorkflowTaskStatus.EXITED: return False return True @@ -71,10 +71,10 @@ class WorkflowTask(EmbeddedObject): self.exit() def enter(self): - if self.status is not WorkflowTask.Status.READY: + if self.status is not WorkflowTaskStatus.READY: raise Exception('Attempted to enter a task when not ready') - self.status = WorkflowTask.Status.ENTERED + self.status = WorkflowTaskStatus.ENTERED self.fire_event('enter') self.on_enter() @@ -86,10 +86,10 @@ class WorkflowTask(EmbeddedObject): self.exited_at = DateTimeField.now() def exit(self): - if self.status is not WorkflowTask.Status.ENTERED: + if self.status is not WorkflowTaskStatus.ENTERED: raise Exception('Attempted to exit a task when not entered') - self.status = WorkflowTask.Status.EXITED + self.status = WorkflowTaskStatus.EXITED self.fire_event('exit') self.on_exit() @@ -146,7 +146,7 @@ class TaskConfigureElection(WorkflowTask): label = 'Freeze the election' #def on_enter(self): - # self.status = WorkflowTask.Status.COMPLETE + # self.status = WorkflowTaskStatus.COMPLETE class TaskOpenVoting(WorkflowTask): label = 'Open voting' diff --git a/eos/core/objects/__init__.py b/eos/core/objects/__init__.py index 87870ec..e91c506 100644 --- a/eos/core/objects/__init__.py +++ b/eos/core/objects/__init__.py @@ -503,3 +503,61 @@ class TopLevelObject(DocumentObject, metaclass=TopLevelObjectType): class EmbeddedObject(DocumentObject): pass + +# Enums +# ===== + +class EosEnumType(EosObjectType): + def __new__(meta, name, bases, attrs): + cls = EosObjectType.__new__(meta, name, bases, attrs) + + cls._values = {} + + for attr in list(dir(cls)): + val = getattr(cls, attr); + if isinstance(val, int): + instance = cls(attr, val) + setattr(cls, attr, instance) + cls._values[val] = instance + + return cls + +class EosEnum(EosObject, metaclass=EosEnumType): + def __init__(self, name, value): + super().__init__() + self.name = name + self.value = value + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.value == other.value + def __ne__(self, other): + if not isinstance(other, self.__class__): + return True + return self.value != other.value + def __gt__(self, other): + if not isinstance(other, self.__class__): + raise TypeError + return self.value > other.value + def __lt__(self, other): + if not isinstance(other, self.__class__): + raise TypeError + return self.value < other.value + def __ge__(self, other): + if not isinstance(other, self.__class__): + raise TypeError + return self.value >= other.value + def __le__(self, other): + if not isinstance(other, self.__class__): + raise TypeError + return self.value <= other.value + + def serialise(self, options=SerialiseOptions.DEFAULT): + return self.value + + @classmethod + def deserialise(cls, value): + return cls._values[value] + +EnumField = EmbeddedObjectField diff --git a/eos/core/tasks/__init__.py b/eos/core/tasks/__init__.py index 3e7d3a3..412c25a 100644 --- a/eos/core/tasks/__init__.py +++ b/eos/core/tasks/__init__.py @@ -16,17 +16,20 @@ from eos.core.objects import * -class Task(TopLevelObject): - class Status: - UNKNOWN = 0 - - READY = 20 - PROCESSING = 30 - COMPLETE = 50 - - FAILED = -10 - TIMEOUT = -20 +class TaskStatus(EosEnum): + UNKNOWN = 0 + READY = 20 + PROCESSING = 30 + COMPLETE = 50 + + FAILED = -10 + TIMEOUT = -20 + + def is_error(self): + return self.value < 0 + +class Task(TopLevelObject): label = 'Unknown task' _id = UUIDField() @@ -37,7 +40,7 @@ class Task(TopLevelObject): started_at = DateTimeField() completed_at = DateTimeField() - status = IntField(default=0) + status = EnumField(TaskStatus) messages = ListField(StringField()) def run(self): @@ -74,7 +77,7 @@ class TaskScheduler: tasks = Task.get_all() for task in tasks: - if task.status == Task.Status.READY: + if task.status == TaskStatus.READY: pending_tasks.append(task) # Sort them to ensure we iterate over them in the correct order @@ -88,7 +91,7 @@ class TaskScheduler: tasks = Task.get_all() for task in tasks: - if task.status == Task.Status.PROCESSING: + if task.status == TaskStatus.PROCESSING: active_tasks.append(task) return active_tasks @@ -99,7 +102,7 @@ class TaskScheduler: tasks = Task.get_all() for task in tasks: - if task.status == Task.Status.COMPLETE or task.status < 0: + if task.status == TaskStatus.COMPLETE or task.status.is_error(): completed_tasks.append(task) if limit: diff --git a/eos/core/tasks/direct.py b/eos/core/tasks/direct.py index 086f0ae..ea8de1f 100644 --- a/eos/core/tasks/direct.py +++ b/eos/core/tasks/direct.py @@ -19,19 +19,19 @@ from eos.core.objects import * class DirectRunStrategy(RunStrategy): def run(self, task): - task.status = Task.Status.PROCESSING + task.status = TaskStatus.PROCESSING task.started_at = DateTimeField.now() task.save() try: task._run() - task.status = Task.Status.COMPLETE + task.status = TaskStatus.COMPLETE task.completed_at = DateTimeField.now() task.save() task.complete() except Exception as e: - task.status = Task.Status.FAILED + task.status = TaskStatus.FAILED task.completed_at = DateTimeField.now() if is_python: #__pragma__('skip') diff --git a/eos/core/tests.py b/eos/core/tests.py index 475eedd..e818a40 100644 --- a/eos/core/tests.py +++ b/eos/core/tests.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -144,7 +144,7 @@ class TaskTestCase(EosTestCase): task.save() task.run() - self.assertEqual(task.status, Task.Status.COMPLETE) + self.assertEqual(task.status, TaskStatus.COMPLETE) self.assertEqual(len(task.messages), 1) self.assertEqual(task.messages[0], 'Hello World') self.assertEqual(task.result, 'Success') @@ -158,6 +158,6 @@ class TaskTestCase(EosTestCase): task.save() task.run() - self.assertEqual(task.status, Task.Status.FAILED) + self.assertEqual(task.status, TaskStatus.FAILED) self.assertEqual(len(task.messages), 1) self.assertTrue('Test exception' in task.messages[0]) diff --git a/eos/psr/tests.py b/eos/psr/tests.py index 4892511..533a89b 100644 --- a/eos/psr/tests.py +++ b/eos/psr/tests.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -224,13 +224,13 @@ class ElectionTestCase(EosTestCase): cls.db_connect_and_reset() def do_task_assert(self, election, task, next_task): - self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.READY) + self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.READY) if next_task is not None: - self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.NOT_READY) + self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.NOT_READY) election.workflow.get_task(task).enter() - self.assertEqual(election.workflow.get_task(task).status, WorkflowTask.Status.EXITED) + self.assertEqual(election.workflow.get_task(task).status, WorkflowTaskStatus.EXITED) if next_task is not None: - self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTask.Status.READY) + self.assertEqual(election.workflow.get_task(next_task).status, WorkflowTaskStatus.READY) @py_only def test_run_election(self): diff --git a/eosweb/core/main.py b/eosweb/core/main.py index c60775d..5e6c2e4 100644 --- a/eosweb/core/main.py +++ b/eosweb/core/main.py @@ -172,7 +172,7 @@ def tally_stv_election(electionid, qnum, randfile): q_num=qnum, random=dat, num_seats=7, - status=Task.Status.READY, + status=TaskStatus.READY, run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])() ) task.save() @@ -260,13 +260,13 @@ def election_admin_summary(election): @election_admin def election_admin_enter_task(election): workflow_task = election.workflow.get_task(flask.request.args['task_name']) - if workflow_task.status != WorkflowTask.Status.READY: + if workflow_task.status != WorkflowTaskStatus.READY: return flask.Response('Task is not yet ready or has already exited', 409) task = WorkflowTaskEntryWebTask( election_id=election._id, workflow_task=workflow_task._name, - status=Task.Status.READY, + status=TaskStatus.READY, run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])() ) task.run() @@ -283,7 +283,7 @@ def election_admin_schedule_task(election): election_id=election._id, workflow_task=workflow_task._name, run_at=DateTimeField().deserialise(flask.request.form['datetime']), - status=Task.Status.READY, + status=TaskStatus.READY, run_strategy=EosObject.lookup(app.config['TASK_RUN_STRATEGY'])() ) task.save() @@ -293,7 +293,7 @@ def election_admin_schedule_task(election): @app.route('/election//cast_ballot', methods=['POST']) @using_election def election_api_cast_vote(election): - if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status < WorkflowTask.Status.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status > WorkflowTask.Status.READY: + if election.workflow.get_task('eos.base.workflow.TaskOpenVoting').status < WorkflowTaskStatus.EXITED or election.workflow.get_task('eos.base.workflow.TaskCloseVoting').status > WorkflowTaskStatus.READY: # Voting is not yet open or has closed return flask.Response('Voting is not yet open or has closed', 409) diff --git a/eosweb/core/templates/base.html b/eosweb/core/templates/base.html index 0ee6d7e..706dff1 100644 --- a/eosweb/core/templates/base.html +++ b/eosweb/core/templates/base.html @@ -2,7 +2,7 @@ {# Eos - Verifiable elections - Copyright © 2017 RunasSudo (Yingtong Li) + Copyright © 2017-18 RunasSudo (Yingtong Li) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by @@ -29,7 +29,7 @@ Source Code {% if session.user %} {% if session.user.is_admin() %} - {% include 'active_tasks_menu.html' %} + {% include 'task/active_tasks_menu.html' %} {% endif %}