From a023cce7ec486c85234bc25ad1740191c920d454 Mon Sep 17 00:00:00 2001 From: erick Date: Fri, 30 Oct 2009 22:21:54 +0000 Subject: Tutorial: Changed Tutorial.update_* to update properties instead of replacing the object, Moved validation of state name to a decorator to avoid dupliation --- (limited to 'tutorius') 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 -- cgit v0.9.1