Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerick <erick@sugar-dev-erick.(none)>2009-10-30 22:21:54 (GMT)
committer erick <erick@sugar-dev-erick.(none)>2009-10-30 22:21:54 (GMT)
commita023cce7ec486c85234bc25ad1740191c920d454 (patch)
treed6ec477c638771e8545bde5d7338c9d5073a15db
parent37e2ab5dd552be9aec49ccf774c90da8b962ea9f (diff)
Tutorial: Changed Tutorial.update_* to update properties instead of replacing the object, Moved validation of state name to a decorator to avoid dupliation
-rw-r--r--tests/tutorialtests.py68
-rw-r--r--tutorius/constraints.py22
-rw-r--r--tutorius/properties.py10
-rw-r--r--tutorius/tutorial.py171
4 files changed, 187 insertions, 84 deletions
diff --git a/tests/tutorialtests.py b/tests/tutorialtests.py
index c1f591f..cd0ef6f 100644
--- a/tests/tutorialtests.py
+++ b/tests/tutorialtests.py
@@ -60,11 +60,21 @@ from sugar.tutorius.tutorial import *
# the tests will break each time we change something in the
# implementation
+from sugar.tutorius.properties import *
+
+class MockAction(TPropContainer):
+ i = TIntProperty(0, 0, 9)
+
+class MockEvent(TPropContainer):
+ i = TIntProperty(0, 0, 9)
+
class StateTest(unittest.TestCase):
"""Test basic functionalities of states used by tutorials"""
def setUp(self):
self.state = State("State1")
+ self.action = MockAction()
+ self.event = MockEvent()
def tearDown(self):
pass
@@ -86,11 +96,16 @@ class StateTest(unittest.TestCase):
assert action_name1 != action_name2
def test_update_dummy_action(self):
- action_name = self.state.add_action("action1")
- self.state.update_action(action_name, "action2")
+ action_name = self.state.add_action(self.action)
+ assert self.action.i == 0
+
+ prop = self.action.get_properties_dict_copy()
+ prop["i"] = 2
+ self.state.update_action(action_name, prop)
+
assert len(self.state.get_action_dict()) == 1
assert self.state.get_action_dict().has_key(action_name)
- assert self.state.get_action_dict()[action_name] == "action2"
+ assert self.state.get_action_dict()[action_name].get_properties_dict_copy() == prop
def test_delete_dummy_action(self):
action_name = self.state.add_action("action1")
@@ -123,11 +138,22 @@ class StateTest(unittest.TestCase):
assert transition_name1 != transition_name2
def test_update_dummy_transition(self):
- transition_name = self.state.add_transition("transition1")
- self.state.update_transition(transition_name, "transition2")
+ transition_name = self.state.add_transition((self.event, Tutorial.END))
+ assert self.event.i == 0
+
+ prop = self.event.get_properties_dict_copy()
+ prop["i"] = 2
+ self.state.update_transition(transition_name, prop)
+
assert len(self.state.get_transition_dict()) == 1
assert self.state.get_transition_dict().has_key(transition_name)
- assert self.state.get_transition_dict()[transition_name] == "transition2"
+ assert self.state.get_transition_dict()[transition_name][0].get_properties_dict_copy() == prop
+ assert self.state.get_transition_dict()[transition_name][1] == Tutorial.END
+
+ # Now update only the transition
+ self.state.update_transition(transition_name, new_state=Tutorial.INIT)
+ assert self.state.get_transition_dict()[transition_name][0].get_properties_dict_copy() == prop
+ assert self.state.get_transition_dict()[transition_name][1] == Tutorial.INIT
def test_delete_dummy_transition(self):
transition_name = self.state.add_transition("transition1")
@@ -184,6 +210,8 @@ class TutorialTest(unittest.TestCase):
def setUp(self):
self.tutorial = Tutorial("Tutorial Test")
+ self.action = MockAction()
+ self.event = MockEvent()
def tearDown(self):
pass
@@ -247,7 +275,7 @@ class TutorialTest(unittest.TestCase):
def test_delete_linked_state(self):
state_name1 = self.tutorial.add_state()
self.tutorial.update_transition(Tutorial.INITIAL_TRANSITION_NAME, \
- (Tutorial.AUTOMATIC_TRANSITION_EVENT, state_name1))
+ None, state_name1)
transition_name1 = self.tutorial.add_transition(state_name1,("event1", Tutorial.END))
self.tutorial.delete_state(state_name1)
assert len(self.tutorial.get_state_dict()) == 2
@@ -277,11 +305,15 @@ class TutorialTest(unittest.TestCase):
def test_update_dummy_action(self):
state_name = self.tutorial.add_state()
- action_name = self.tutorial.add_action(state_name,"action1")
- self.tutorial.update_action(action_name, "action2")
+ action_name = self.tutorial.add_action(state_name,self.action)
+
+ prop = self.action.get_properties_dict_copy()
+ prop["i"] = 2
+ self.tutorial.update_action(action_name, prop)
+
assert len(self.tutorial.get_action_dict()) == 1
assert self.tutorial.get_action_dict().has_key(action_name)
- assert self.tutorial.get_action_dict()[action_name] == "action2"
+ assert self.tutorial.get_action_dict()[action_name].get_properties_dict_copy() == prop
def test_delete_dummy_action(self):
state_name = self.tutorial.add_state()
@@ -310,11 +342,21 @@ class TutorialTest(unittest.TestCase):
def test_update_dummy_transition(self):
state_name = self.tutorial.add_state()
- transition_name = self.tutorial.add_transition(state_name,"transition1")
- self.tutorial.update_transition(transition_name, "transition2")
+ transition_name = self.tutorial.add_transition(state_name,(self.event, Tutorial.END))
+
+ prop = self.event.get_properties_dict_copy()
+ prop["i"] = 2
+ self.tutorial.update_transition(transition_name, prop)
+
assert len(self.tutorial.get_transition_dict()) == 2
assert self.tutorial.get_transition_dict().has_key(transition_name)
- assert self.tutorial.get_transition_dict()[transition_name] == "transition2"
+ assert self.tutorial.get_transition_dict()[transition_name][0].get_properties_dict_copy() == prop
+ assert self.tutorial.get_transition_dict()[transition_name][1] == Tutorial.END
+
+ # Now update only the transition
+ self.tutorial.update_transition(transition_name, new_state=Tutorial.INIT)
+ assert self.tutorial.get_transition_dict()[transition_name][0].get_properties_dict_copy() == prop
+ assert self.tutorial.get_transition_dict()[transition_name][1] == Tutorial.INIT
def test_delete_dummy_transition(self):
state_name = self.tutorial.add_state()
diff --git a/tutorius/constraints.py b/tutorius/constraints.py
index e91f23a..519bce8 100644
--- a/tutorius/constraints.py
+++ b/tutorius/constraints.py
@@ -25,6 +25,12 @@ for some properties.
# For the File Constraint
import os
+class ConstraintException(Exception):
+ """
+ Parent class for all constraint exceptions
+ """
+ pass
+
class Constraint():
"""
Basic block for defining constraints on a TutoriusProperty. Every class
@@ -47,7 +53,7 @@ class ValueConstraint(Constraint):
def __init__(self, limit):
self.limit = limit
-class UpperLimitConstraintError(Exception):
+class UpperLimitConstraintError(ConstraintException):
pass
class UpperLimitConstraint(ValueConstraint):
@@ -64,7 +70,7 @@ class UpperLimitConstraint(ValueConstraint):
raise UpperLimitConstraintError()
return
-class LowerLimitConstraintError(Exception):
+class LowerLimitConstraintError(ConstraintException):
pass
class LowerLimitConstraint(ValueConstraint):
@@ -81,7 +87,7 @@ class LowerLimitConstraint(ValueConstraint):
raise LowerLimitConstraintError()
return
-class MaxSizeConstraintError(Exception):
+class MaxSizeConstraintError(ConstraintException):
pass
class MaxSizeConstraint(ValueConstraint):
@@ -99,7 +105,7 @@ class MaxSizeConstraint(ValueConstraint):
raise MaxSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit))
return
-class MinSizeConstraintError(Exception):
+class MinSizeConstraintError(ConstraintException):
pass
class MinSizeConstraint(ValueConstraint):
@@ -117,7 +123,7 @@ class MinSizeConstraint(ValueConstraint):
raise MinSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit))
return
-class ColorConstraintError(Exception):
+class ColorConstraintError(ConstraintException):
pass
class ColorArraySizeError(ColorConstraintError):
@@ -153,7 +159,7 @@ class ColorConstraint(Constraint):
return
-class BooleanConstraintError(Exception):
+class BooleanConstraintError(ConstraintException):
pass
class BooleanConstraint(Constraint):
@@ -165,7 +171,7 @@ class BooleanConstraint(Constraint):
return
raise BooleanConstraintError("Value is not True or False")
-class EnumConstraintError(Exception):
+class EnumConstraintError(ConstraintException):
pass
class EnumConstraint(Constraint):
@@ -190,7 +196,7 @@ class EnumConstraint(Constraint):
raise EnumConstraintError("Value is not part of the enumeration")
return
-class FileConstraintError(Exception):
+class FileConstraintError(ConstraintException):
pass
class FileConstraint(Constraint):
diff --git a/tutorius/properties.py b/tutorius/properties.py
index a675ba9..427222b 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -19,7 +19,7 @@ TutoriusProperties have the same behaviour as python properties (assuming you
also use the TPropContainer), with the added benefit of having builtin dialog
prompts and constraint validation.
"""
-from copy import copy
+from copy import copy, deepcopy
from .constraints import Constraint, \
UpperLimitConstraint, LowerLimitConstraint, \
@@ -93,8 +93,16 @@ class TPropContainer(object):
"""
Return the list of property names.
"""
+ # Why isn't it simply:
+ # return self._props.keys() ?
return object.__getattribute__(self, "_props").keys()
+ def get_properties_dict_copy(self):
+ """
+ Return a deep copy of the dictionary of properties from that object.
+ """
+ return deepcopy(self._props)
+
# Providing the hash methods necessary to use TPropContainers
# in a dictionary, according to their properties
def __hash__(self):
diff --git a/tutorius/tutorial.py b/tutorius/tutorial.py
index 0736bbb..9d8bc27 100644
--- a/tutorius/tutorial.py
+++ b/tutorius/tutorial.py
@@ -20,6 +20,9 @@
#TODO: Check for putting checks for validity of names as decorators
#TODO: For notification of modifications on the Tutorial check for GObject and PyDispatcher for inspiration
+from .constraints import ConstraintException
+
+
class Tutorial(object):
""" This class replaces the previous Tutorial class and
allows manipulation of the abstract representation
@@ -85,6 +88,23 @@ class Tutorial(object):
# existing state names
self._state_name_nb = 0
+ # Validation decorators to assert preconditions
+ def validateStateName(meth):
+ """
+ Assert that the state name found in the first part of the string
+ actually exists
+ """
+ def new(self, name, *args, **kwargs):
+ state_name = name
+
+ if name.find(Tutorial._NAME_SEPARATOR) != -1:
+ state_name = name[:name.find(Tutorial._NAME_SEPARATOR)]
+
+ if not self._state_dict.has_key(state_name):
+ raise LookupError("Tutorial: state <" + str(state_name) +\
+ "> is not defined")
+ return meth(self, name, *args, **kwargs)
+ return new
def add_state(self, action_list=[], transition_list=[]):
"""
@@ -160,37 +180,40 @@ class Tutorial(object):
# The unicity of the transition is validated by the state
return self._state_dict[state_name].add_transition(transition)
- def update_action(self, action_name, new_action):
+ @validateStateName
+ def update_action(self, action_name, new_properties):
"""
- Replace the action with action_name by new_action
-
- The action is validated.
+ Update the action with action_name with a property dictionary
+ new_properties. If one property update is invalid, the old
+ values are restored and an exception is raised.
- @param action_name The name of the action to replace
- @param new_action The action that will replace the old one
- @return The replaced action
+ @param action_name The name of the action to update
+ @param new_properties The properties that will update the action
+ @return old properties from the action
@raise LookupError if action_name doesn't exist
+ @raise ConstraintException if a property constraint is violated
"""
state_name = action_name[:action_name.find(Tutorial._NAME_SEPARATOR)]
- if not self._state_dict.has_key(state_name):
- raise LookupError("Tutorial: action <" + action_name +\
- "> is not defined")
-
- self._validate_action(new_action)
+ #TODO: We should validate that only properties defined on the action
+ # are passed in
- return self._state_dict[state_name].update_action(action_name, new_action)
+ return self._state_dict[state_name].update_action(action_name, new_properties)
- def update_transition(self, transition_name, new_transition):
+ @validateStateName
+ def update_transition(self, transition_name, new_properties=None, new_state=None):
"""
- Replace the transition with transition_name by new_transition
-
- The transition is validated.
+ Update the transition with transition_name with new properties and/or
+ a new state to transition to. A None value means that the corresponding
+ value won't be updated. If one property update is invalid, the old
+ values are restored and an exception is raised.
@param transition_name The name of the transition to replace
- @param new_transition The transition that will replace the old one
- @return The replaced transition
+ @param new_properties The properties that will update the transition
+ @param new_state The new state to transition to
+ @return a tuple (old_properties, old_state) with previous values
@raise LookupError if transition_name doesn't exist
+ @raise ConstraintException if a property constraint is violated
"""
state_name = transition_name[:transition_name.find(Tutorial._NAME_SEPARATOR)]
@@ -198,10 +221,16 @@ class Tutorial(object):
raise LookupError("Tutorial: transition <" + transition_name +\
"> is not defined")
- self._validate_transition(new_transition)
+ if new_state and not self._state_dict.has_key(new_state):
+ raise LookupError("Tutorial: destination state <" + new_state +\
+ "> is not defined")
+
+ #TODO: We should validate that only properties defined on the action
+ # are passed in
- return self._state_dict[state_name].update_transition(transition_name, new_transition)
+ return self._state_dict[state_name].update_transition(transition_name, new_properties, new_state)
+ @validateStateName
def delete_action(self, action_name):
"""
Delete the action identified by action_name.
@@ -212,12 +241,9 @@ class Tutorial(object):
"""
state_name = action_name[:action_name.find(Tutorial._NAME_SEPARATOR)]
- if not self._state_dict.has_key(state_name):
- raise LookupError("Tutorial: action <" + action_name +\
- "> is not defined")
-
return self._state_dict[state_name].delete_action(action_name)
-
+
+ @validateStateName
def delete_transition(self, transition_name):
"""
Delete the transition identified by transition_name.
@@ -228,12 +254,9 @@ class Tutorial(object):
"""
state_name = transition_name[:transition_name.find(Tutorial._NAME_SEPARATOR)]
- if not self._state_dict.has_key(state_name):
- raise LookupError("Tutorial: transition <" + transition_name +\
- "> is not defined")
-
return self._state_dict[state_name].delete_transition(transition_name)
+ @validateStateName
def delete_state(self, state_name):
"""
Delete the state, delete all the actions and transitions
@@ -250,10 +273,6 @@ class Tutorial(object):
"""
if state_name == Tutorial.INIT or state_name == Tutorial.END:
raise StateDeletionError("<" + state_name + "> cannot be deleted")
-
- if not self._state_dict.has_key(state_name):
- raise LookupError("Tutorial: state <" + transition_name +\
- "> is not defined")
next_states = set(self.get_following_states_dict(state_name).values())
previous_states = set(self.get_previous_states_dict(state_name).values())
@@ -519,6 +538,7 @@ class Tutorial(object):
"""
return str(self._state_dict)
+
class State(object):
"""
This is a step in a tutorial. The state represents a collection of actions
@@ -581,25 +601,31 @@ class State(object):
return self._actions.pop(action_name)
else:
raise LookupError("Tutorial.State: action <" + action_name + "> is not defined")
-
- def update_action(self, action_name, new_action):
+
+ def update_action(self, action_name, new_properties):
"""
- Replace the action with action_name by new_action
-
- @param action_name The name of the action to replace
- @param new_action The action that will replace the old one
- @return The replaced action
+ Update the action with action_name with a property dictionary
+ new_properties. If one property update is invalid, the old
+ values are restored and an exception is raised.
+
+ @param action_name The name of the action to update
+ @param new_properties The properties that will update the action
+ @return The old properties from the action
@raise LookupError if action_name doesn't exist
+ @raise ConstraintException if a property constraint is violated
"""
- # TODO: For now let's just replace the action with a new one,
- # we should check to see if we need a replace or an update
- # semantic for this update method
- if self._actions.has_key(action_name):
- old_action = self._actions.pop(action_name)
- self._actions[action_name] = new_action
- return old_action
- else:
+ if not self._actions.has_key(action_name):
raise LookupError("Tutorial.State: action <" + action_name + "> is not defined")
+
+ action = self._actions[action_name]
+ old_properties = action.get_properties_dict_copy()
+ try:
+ for property_name, property_value in new_properties.iteritems():
+ action.__setattr__(property_name, property_value)
+ return old_properties
+ except ConstraintException, e:
+ action._props = old_properties
+ raise e
def get_action_dict(self):
"""
@@ -635,25 +661,46 @@ class State(object):
self._transitions[transition_name] = new_transition
return transition_name
- def update_transition(self, transition_name, new_transition):
+ def update_transition(self, transition_name, new_properties=None, new_state=None):
"""
- Replace the transition with transition_name by new_transition
-
+ Update the transition with transition_name with new properties and/or
+ a new state to transition to. A None value means that the corresponding
+ value won't be updated. If one property update is invalid, the old
+ values are restored and an exception is raised.
+
@param transition_name The name of the transition to replace
- @param new_transition The transition that will replace the old one
- @return The replaced transition
+ @param new_properties The properties that will update the event on the transition
+ @param new_state The new state to transition to
+ @return a tuple (old_properties, old_state) with previous values
@raise LookupError if transition_name doesn't exist
+ @raise ConstraintException if a property constraint is violated
"""
- # TODO: For now let's just replace the transition with a new one,
- # we should check to see if we need a replace or an update
- # semantic for this update method
- if self._transitions.has_key(transition_name):
- old_transition = self._transitions.pop(transition_name)
- self._transitions[transition_name] = new_transition
- return old_transition
- else:
+ if not self._transitions.has_key(transition_name):
raise LookupError("Tutorial.State: transition <" + transition_name + "> is not defined")
+ transition = self._transitions[transition_name]
+
+ tmp_event = transition[0]
+ tmp_state = transition[1]
+
+ old_properties = transition[0].get_properties_dict_copy()
+ old_state = transition[1]
+
+ if new_properties:
+ try:
+ for property_name, property_value in new_properties.iteritems():
+ tmp_event.__setattr__(property_name, property_value)
+ except ConstraintException, e:
+ tmp_event._props = old_properties
+ raise e
+
+ if new_state:
+ tmp_state = new_state
+
+ self._transitions[transition_name] = (tmp_event, tmp_state)
+
+ return (old_properties, old_state)
+
def delete_transition(self, transition_name):
"""
Delete the transition with the name transition_name