diff options
author | ptmcg <ptmcg@austin.rr.com> | 2018-12-31 13:10:59 -0600 |
---|---|---|
committer | ptmcg <ptmcg@austin.rr.com> | 2018-12-31 13:10:59 -0600 |
commit | e177e2feed1bfbe29f32c3378978313d19ce1c26 (patch) | |
tree | 45eed16a9965597827d8dc2031b1012d222fc842 /examples/statemachine | |
parent | 5132a91c470a8b6c34c7f0525c0bf41b9365e817 (diff) | |
download | pyparsing-git-e177e2feed1bfbe29f32c3378978313d19ce1c26.tar.gz |
Add document signoff and library book state examples;
Diffstat (limited to 'examples/statemachine')
-rw-r--r-- | examples/statemachine/documentSignoffDemo.py | 68 | ||||
-rw-r--r-- | examples/statemachine/documentsignoffstate.pystate | 71 | ||||
-rw-r--r-- | examples/statemachine/libraryBookDemo.py | 78 | ||||
-rw-r--r-- | examples/statemachine/librarybookstate.pystate | 19 | ||||
-rw-r--r-- | examples/statemachine/statemachine.py | 60 | ||||
-rw-r--r-- | examples/statemachine/trafficLightDemo.py | 5 | ||||
-rw-r--r-- | examples/statemachine/trafficlightstate.pystate | 14 |
7 files changed, 277 insertions, 38 deletions
diff --git a/examples/statemachine/documentSignoffDemo.py b/examples/statemachine/documentSignoffDemo.py new file mode 100644 index 0000000..9378507 --- /dev/null +++ b/examples/statemachine/documentSignoffDemo.py @@ -0,0 +1,68 @@ +# +# documentSignoffDemo.py +# +# Example of a state machine modeling the state of a document in a document +# control system, using named state transitions +# +import statemachine +import documentsignoffstate + + +class Document: + def __init__(self): + # start light in Red state + self._state = documentsignoffstate.New() + + @property + def state(self): + return self._state + + # get behavior/properties from current state + def __getattr__(self, attrname): + attr = getattr(self._state, attrname) + if isinstance(getattr(documentsignoffstate, attrname, None), + documentsignoffstate.DocumentRevisionStateTransition): + return lambda : setattr(self, '_state', attr()) + return attr + + def __str__(self): + return "{}: {}".format(self.__class__.__name__, self._state) + + +def run_demo(): + import random + + doc = Document() + print(doc) + + # begin editing document + doc.create() + print(doc) + print(doc.state.description) + + while not isinstance(doc._state, documentsignoffstate.Approved): + + print('...submit') + doc.submit() + print(doc) + print(doc.state.description) + + if random.randint(1,10) > 3: + print('...reject') + doc.reject() + else: + print('...approve') + doc.approve() + + print(doc) + print(doc.state.description) + + doc.activate() + print(doc) + print(doc.state.description) + +if __name__ == '__main__': + run_demo() + +# TODO - can you pickle state or otherwise persist it? +# TODO - can you decorate state changes (for logging or permission checking)? diff --git a/examples/statemachine/documentsignoffstate.pystate b/examples/statemachine/documentsignoffstate.pystate new file mode 100644 index 0000000..04df274 --- /dev/null +++ b/examples/statemachine/documentsignoffstate.pystate @@ -0,0 +1,71 @@ +# +# documentsignoffstate.pystate +# +# state machine model of the states and associated behaviors and properties for each +# different state of a document in a document control system +# +# example using named state transitions + +# This implements a state model for submitting, +# approving, activating, and purging document +# revisions in a document management system. +# +# The state model looks like: +# +# New +# | +# | (create) +# | +# v +# Editing ----------------------------------------------+ +# | ^ | +# | | | +# | +----------+ | +# | | | +# | (submit) | | (cancel) +# | | (reject) | +# v | | +# PendingApproval-+ | +# | | +# | (approve) | +# | | +# v | +# Approved <--------------------------+ (deactivate) | +# | | | | +# | +--------------+ | | +# | | (activate) | | +# | v | | +# | (retire) Active ----------+ | +# | | +# v | +# Retired | +# | | +# | (purge) | +# | | +# v | +# Deleted <---------------------------------------------+ +# +# +# There is no behavior attached to these states, this is +# just an example of a state machine with named transitions. +# + + +statemachine DocumentRevisionState: + New -( create )-> Editing + Editing -( cancel )-> Deleted + Editing -( submit )-> PendingApproval + PendingApproval -( reject )-> Editing + PendingApproval -( approve )-> Approved + Approved -( activate )-> Active + Active -( deactivate )-> Approved + Approved -( retire )-> Retired + Retired -( purge )-> Deleted + +New.description = 'creating...' +Editing.description = 'editing...' +PendingApproval.description = 'reviewing...' +Approved.description = 'approved/inactive...' +Active.description = 'approved/active...' +Deleted.description = 'deleted...' +Retired.description = 'retired...'
\ No newline at end of file diff --git a/examples/statemachine/libraryBookDemo.py b/examples/statemachine/libraryBookDemo.py new file mode 100644 index 0000000..84108e5 --- /dev/null +++ b/examples/statemachine/libraryBookDemo.py @@ -0,0 +1,78 @@ +import statemachine +import librarybookstate + + +class Book: + def __init__(self): + self._state = librarybookstate.New() + + @property + def state(self): + return self._state + + # get behavior/properties from current state + def __getattr__(self, attrname): + attr = getattr(self._state, attrname) + if isinstance(getattr(librarybookstate, attrname, None), + librarybookstate.BookStateTransition): + return lambda: setattr(self, '_state', attr()) + return attr + + def __str__(self): + return "{}: {}".format(self.__class__.__name__, self._state) + + +class RestrictedBook(Book): + def __init__(self): + super(RestrictedBook, self).__init__() + self._authorized_users = [] + + def authorize(self, name): + self._authorized_users.append(name) + + # specialized checkout to check permission of user first + def checkout(self, user=None): + if user in self._authorized_users: + self._state = self._state.checkout() + else: + raise Exception("{} could not check out restricted book".format((user, "anonymous")[user is None])) + + +def run_demo(): + book = Book() + book.shelve() + print(book) + book.checkout() + print(book) + book.checkin() + print(book) + book.reserve() + print(book) + try: + book.checkout() + except statemachine.InvalidTransitionException: + print('..cannot check out reserved book') + book.release() + print(book) + book.checkout() + print(book) + print() + + restricted_book = RestrictedBook() + restricted_book.authorize("BOB") + restricted_book.restrict() + print(restricted_book) + for name in [None, "BILL", "BOB"]: + try: + restricted_book.checkout(name) + except Exception as e: + print('..' + str(e)) + else: + print('checkout to', name) + print(restricted_book) + restricted_book.checkin() + print(restricted_book) + + +if __name__ == '__main__': + run_demo() diff --git a/examples/statemachine/librarybookstate.pystate b/examples/statemachine/librarybookstate.pystate new file mode 100644 index 0000000..24f07ed --- /dev/null +++ b/examples/statemachine/librarybookstate.pystate @@ -0,0 +1,19 @@ +# +# librarybookstate.pystate +# +# This state machine models the state of books in a library. +# + +statemachine BookState: + New -(shelve)-> Available + Available -(reserve)-> OnHold + OnHold -(release)-> Available + Available -(checkout)-> CheckedOut + CheckedOut -(checkin)-> Available + + # add states for restricted books + New -(restrict)-> Restricted + Available -(restrict)-> Restricted + Restricted -(release)-> Available + Restricted -(checkout)-> CheckedOutRestricted + CheckedOutRestricted -(checkin)-> Restricted diff --git a/examples/statemachine/statemachine.py b/examples/statemachine/statemachine.py index 62ad22b..b7b60d2 100644 --- a/examples/statemachine/statemachine.py +++ b/examples/statemachine/statemachine.py @@ -25,6 +25,8 @@ from pyparsing import Word, Group, ZeroOrMore, alphas, \ Empty, LineEnd, OneOrMore, col, Keyword, pythonStyleComment, \ StringEnd, traceParseAction +class InvalidTransitionException(Exception): pass + ident = Word(alphas + "_", alphanums + "_$") def no_keywords_allowed(s, l, t): @@ -33,13 +35,13 @@ def no_keywords_allowed(s, l, t): ident.addCondition(no_keywords_allowed, message="cannot use a Python keyword for state or transition identifier") -stateTransition = ident("fromState") + "->" + ident("toState") +stateTransition = ident("from_state") + "->" + ident("to_state") stateMachine = (Keyword("statemachine") + ident("name") + ":" + OneOrMore(Group(stateTransition))("transitions")) -namedStateTransition = (ident("fromState") +namedStateTransition = (ident("from_state") + "-(" + ident("transition") + ")->" - + ident("toState")) + + ident("to_state")) namedStateMachine = (Keyword("statemachine") + ident("name") + ":" + OneOrMore(Group(namedStateTransition))("transitions")) @@ -52,9 +54,9 @@ def expand_state_definition(source, loc, tokens): states = set() fromTo = {} for tn in tokens.transitions: - states.add(tn.fromState) - states.add(tn.toState) - fromTo[tn.fromState] = tn.toState + states.add(tn.from_state) + states.add(tn.to_state) + fromTo[tn.from_state] = tn.to_state # define base class for state classes baseStateClass = tokens.name @@ -62,8 +64,12 @@ def expand_state_definition(source, loc, tokens): "class %s(object):" % baseStateClass, " def __str__(self):", " return self.__class__.__name__", + " @classmethod", + " def states(cls):", + " return list(cls.__subclasses__)", " def next_state(self):", - " return self._next_state_class()"]) + " return self._next_state_class()", + ]) # define all state classes statedef.extend("class {}({}): pass".format(s, baseStateClass) for s in states) @@ -87,13 +93,13 @@ def expand_named_state_definition(source, loc, tokens): fromTo = {} for tn in tokens.transitions: - states.add(tn.fromState) - states.add(tn.toState) + states.add(tn.from_state) + states.add(tn.to_state) transitions.add(tn.transition) - if tn.fromState in fromTo: - fromTo[tn.fromState][tn.transition] = tn.toState + if tn.from_state in fromTo: + fromTo[tn.from_state][tn.transition] = tn.to_state else: - fromTo[tn.fromState] = {tn.transition: tn.toState} + fromTo[tn.from_state] = {tn.transition: tn.to_state} # add entries for terminal states for s in states: @@ -115,35 +121,32 @@ def expand_named_state_definition(source, loc, tokens): # define base class for state classes excmsg = "'" + tokens.name + \ '.%s does not support transition "%s"' \ - "'% (self, tn)" + "'% (self, name)" statedef.extend([ "class %s(object):" % baseStateClass, " def __str__(self):", " return self.__class__.__name__", - " def next_state(self,tn):", + " def next_state(self, name):", " try:", " return self.tnmap[tn]()", " except KeyError:", - " raise Exception(%s)" % excmsg, - " def __getattr__(self,name):", - " raise Exception(%s)" % excmsg, + " import statemachine", + " raise statemachine.InvalidTransitionException(%s)" % excmsg, + " def __getattr__(self, name):", + " import statemachine", + " raise statemachine.InvalidTransitionException(%s)" % excmsg, ]) # define all state classes - for s in states: - statedef.append("class %s(%s): pass" % - (s, baseStateClass)) + statedef.extend("class %s(%s): pass" % (s, baseStateClass) + for s in states) # define state transition maps and transition methods for s in states: trns = list(fromTo[s].items()) - statedef.append("%s.tnmap = {%s}" % - (s, ",".join("%s:%s" % tn for tn in trns))) - statedef.extend([ - "%s.%s = staticmethod(lambda : %s())" % - (s, tn_, to_) - for tn_, to_ in trns - ]) + statedef.append("%s.tnmap = {%s}" % (s, ", ".join("%s:%s" % tn for tn in trns))) + statedef.extend("%s.%s = staticmethod(lambda: %s())" % (s, tn_, to_) + for tn_, to_ in trns) return indent + ("\n" + indent).join(statedef) + "\n" @@ -253,4 +256,5 @@ class PystateImporter(SuffixImporter): PystateImporter.register() -# print("registered {!r} importer".format(PystateImporter.suffix)) +if DEBUG: + print("registered {!r} importer".format(PystateImporter.suffix)) diff --git a/examples/statemachine/trafficLightDemo.py b/examples/statemachine/trafficLightDemo.py index ea42180..4fc737c 100644 --- a/examples/statemachine/trafficLightDemo.py +++ b/examples/statemachine/trafficLightDemo.py @@ -26,9 +26,8 @@ class TrafficLight: light = TrafficLight() for i in range(10): - print(light, end=' ') - print(("STOP", "GO")[light.carsCanGo]) - light.crossingSignal() + print("{} {}".format(light, ("STOP", "GO")[light.cars_can_go])) + light.crossing_signal() light.delay() print() diff --git a/examples/statemachine/trafficlightstate.pystate b/examples/statemachine/trafficlightstate.pystate index d0f0a35..8790189 100644 --- a/examples/statemachine/trafficlightstate.pystate +++ b/examples/statemachine/trafficlightstate.pystate @@ -18,21 +18,21 @@ statemachine TrafficLightState: # define some class level constants -Red.carsCanGo = False -Yellow.carsCanGo = True -Green.carsCanGo = True +Red.cars_can_go = False +Yellow.cars_can_go = True +Green.cars_can_go = True # setup some class level methods -def flashCrosswalk(s): +def flash_crosswalk(s): def flash(): print("%s...%s...%s" % (s, s, s)) return flash -Red.crossingSignal = staticmethod(flashCrosswalk("WALK")) -Yellow.crossingSignal = staticmethod(flashCrosswalk("DONT WALK")) -Green.crossingSignal = staticmethod(flashCrosswalk("DONT WALK")) +Red.crossing_signal = staticmethod(flash_crosswalk("WALK")) +Yellow.crossing_signal = staticmethod(flash_crosswalk("DONT WALK")) +Green.crossing_signal = staticmethod(flash_crosswalk("DONT WALK")) # setup some instance methods |