summaryrefslogtreecommitdiff
path: root/examples/statemachine
diff options
context:
space:
mode:
authorptmcg <ptmcg@austin.rr.com>2018-12-31 13:10:59 -0600
committerptmcg <ptmcg@austin.rr.com>2018-12-31 13:10:59 -0600
commite177e2feed1bfbe29f32c3378978313d19ce1c26 (patch)
tree45eed16a9965597827d8dc2031b1012d222fc842 /examples/statemachine
parent5132a91c470a8b6c34c7f0525c0bf41b9365e817 (diff)
downloadpyparsing-git-e177e2feed1bfbe29f32c3378978313d19ce1c26.tar.gz
Add document signoff and library book state examples;
Diffstat (limited to 'examples/statemachine')
-rw-r--r--examples/statemachine/documentSignoffDemo.py68
-rw-r--r--examples/statemachine/documentsignoffstate.pystate71
-rw-r--r--examples/statemachine/libraryBookDemo.py78
-rw-r--r--examples/statemachine/librarybookstate.pystate19
-rw-r--r--examples/statemachine/statemachine.py60
-rw-r--r--examples/statemachine/trafficLightDemo.py5
-rw-r--r--examples/statemachine/trafficlightstate.pystate14
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