fysom-2.1.2/0000755011710100017500000000000012564100143013420 5ustar mriehladmins00000000000000fysom-2.1.2/README0000644011710100017500000003057712564077734014337 0ustar mriehladmins00000000000000.. image:: https://travis-ci.org/mriehl/fysom.png?branch=master :alt: Travis build status image :align: left :target: https://travis-ci.org/mriehl/fysom .. image:: https://coveralls.io/repos/mriehl/fysom/badge.png?branch=master :target: https://coveralls.io/r/mriehl/fysom?branch=master :alt: Coverage status .. image:: https://badge.fury.io/py/fysom.png :target: https://badge.fury.io/py/fysom :alt: Latest PyPI version License ======= MIT licensed. All credits go to Jake Gordon for the `original javascript implementation `_ and to Mansour Behabadi for the `python port `_. Synopsis ======== This is basically Mansours' implementation with unit tests and a build process added. It's also on PyPi (``pip install fysom``). Fysom is built and tested on python 2.6 to 3.3 and PyPy. Installation ============ From your friendly neighbourhood cheeseshop ------------------------------------------- :: pip install fysom Developer setup --------------- This module uses `PyBuilder `_. :: sudo pip install pyb_init pyb-init github mriehl : fysom Running the tests ----------------- :: pyb verify Generating and using a setup.py ------------------------------- :: pyb cd target/dist/fysom-$VERSION ./setup.py bdist_rpm #build RPM Looking at the coverage ----------------------- :: pyb cat target/reports/coverage USAGE ===== Basics ------ :: from fysom import Fysom fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) ... will create an object with a method for each event: - fsm.warn() - transition from 'green' to 'yellow' - fsm.panic() - transition from 'yellow' to 'red' - fsm.calm() - transition from 'red' to 'yellow' - fsm.clear() - transition from 'yellow' to 'green' along with the following members: - fsm.current - contains the current state - fsm.isstate(s) - return True if state s is the current state - fsm.can(e) - return True if event e can be fired in the current state - fsm.cannot(e) - return True if event s cannot be fired in the current state Shorter Syntax -------------- It's possible to define event transitions as 3-tuples (event name, source state, destination state) rather than dictionaries. ``Fysom`` constructor accepts also keyword arguments ``initial``, ``events``, ``callbacks``, and ``final``. This is a shorter version of the previous example:: fsm = Fysom(initial='green', events=[('warn', 'green', 'yellow'), ('panic', 'yellow', 'red'), ('calm', 'red', 'yellow'), ('clear', 'yellow', 'green')]) Initialization -------------- How the state machine should initialize can depend on your application requirements, so the library provides a number of simple options. By default, if you don't specify any initial state, the state machine will be in the 'none' state and you would need to provide an event to take it out of this state: :: fsm = Fysom({'events': [ {'name': 'startup', 'src': 'none', 'dst': 'green'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}]}) print fsm.current # "none" fsm.startup() print fsm.current # "green" If you specify the name of your initial event (as in all the earlier examples), then an implicit 'startup' event will be created for you and fired when the state machine is constructed: :: fsm = Fysom({'initial': 'green', 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}]}) print fsm.current # "green" If your object already has a startup method, you can use a different name for the initial event: :: fsm = Fysom({'initial': {'state': 'green', 'event': 'init'}, 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}]}) print fsm.current # "green" Finally, if you want to wait to call the initial state transition event until a later date, you can defer it: :: fsm = Fysom({'initial': {'state': 'green', 'event': 'init', 'defer': True}, 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}]}) print fsm.current # "none" fsm.init() print fsm.current # "green" Of course, we have now come full circle, this last example pretty much functions the same as the first example in this section where you simply define your own startup event. So you have a number of choices available to you when initializing your state machine. You can also indicate which state should be considered final. This has no effect on the state machine, but lets you use a shorthand method is_finished() that returns true if the state machine is in this 'final' state: :: fsm = Fysom({'initial': 'green', 'final': 'red', 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}]}) print fsm.current # "green" fsm.is_finished() # False fsm.panic() fsm.is_finished() # True Dynamically generated event names --------------------------------- Sometimes you have to compute the name of an event you want to trigger on the fly. Instead of relying on `getattr` you can use the `trigger` method, which takes a string (the event name) as a parameter, followed by any arguments/keyword arguments you want to pass to the event method. This is also arguably better if you're not sure if the event exists at all (FysomError vs. AttributeError, see below). :: from fysom import Fysom fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) fsm.trigger('warn', msg="danger") # equivalent to fsm.warn(msg="danger") fsm.trigger('unknown') # FysomError, event does not exist fsm.unknown() # AttributeError, event does not exist Multiple source and destination states for a single event --------------------------------------------------------- :: fsm = Fysom({'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'rest', 'src': ['hungry', 'satisfied', 'full', 'sick'], 'dst': 'hungry'}]}) This example will create an object with 2 event methods: - fsm.eat() - fsm.rest() The rest event will always transition to the hungry state, while the eat event will transition to a state that is dependent on the current state. NOTE the rest event in the above example can also be specified as multiple events with the same name if you prefer the verbose approach. NOTE if an event can be triggered from any state, you can specify it using the '*' wildcard, or even by omitting the src attribute from its definition: :: fsm = Fysom({'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'eat_a_lot', 'src': '*', 'dst': 'sick'}, {'name': 'rest', 'dst': 'hungry'}]}) NOTE if an event will not change the current state, you can specify destination using the '=' symbol. It's useful when using wildcard source or multiply sources: :: fsm = Fysom({'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'eat_a_little', 'src': '*', 'dst': '='}, {'name': 'eat_a_little', 'src': ['full', 'satisfied'], 'dst': '='}, {'name': 'eat_a_little', 'src': 'hungry', 'dst': '='}, {'name': 'rest', 'dst': 'hungry'}]}) Callbacks --------- 5 callbacks are available if your state machine has methods using the following naming conventions: - onbefore\_event\_ - fired before the *event* - onleave\_state\_ - fired when leaving the old *state* - onenter\_state\_ - fired when entering the new *state* - onreenter\_state\_ - fired when reentering the old *state* (a reflexive transition i.e. src == dst) - onafter\_event\_ - fired after the *event* You can affect the event in 2 ways: - return False from an onbefore\_event\_ handler to cancel the event. This will raise a fysom.Canceled exception. - return False from an onleave\_state\_ handler to perform an asynchronous state transition (see next section) For convenience, the 2 most useful callbacks can be shortened: - on\_event\_ - convenience shorthand for onafter\_event\_ - on\_state\_ - convenience shorthand for onenter\_state\_ In addition, a generic onchangestate() callback can be used to call a single function for all state changes. All callbacks will be passed one argument 'e' which is an object with following attributes: - fsm Fysom object calling the callback - event Event name - src Source state - dst Destination state - (any other keyword arguments you passed into the original event method) - (any positional argument you passed in the original event method, in the 'args' attribute of the event) Note that when you call an event, only one instance of 'e' argument is created and passed to all 4 callbacks. This allows you to preserve data across a state transition by storing it in 'e'. It also allows you to shoot yourself in the foot if you're not careful. Callbacks can be specified when the state machine is first created: :: def onpanic(e): print 'panic! ' + e.msg def oncalm(e): print 'thanks to ' + e.msg + ' done by ' + e.args[0] def ongreen(e): print 'green' def onyellow(e): print 'yellow' def onred(e): print 'red' fsm = Fysom({'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'}], 'callbacks': { 'onpanic': onpanic, 'oncalm': oncalm, 'ongreen': ongreen, 'onyellow': onyellow, 'onred': onred }}) fsm.panic(msg='killer bees') fsm.calm('bob', msg='sedatives in the honey pots') Additionally, they can be added and removed from the state machine at any time: :: def printstatechange(e): print 'event: %s, src: %s, dst: %s' % (e.event, e.src, e.dst) del fsm.ongreen del fsm.onyellow del fsm.onred fsm.onchangestate = printstatechange Asynchronous state transitions ------------------------------ Sometimes, you need to execute some asynchronous code during a state transition and ensure the new state is not entered until you code has completed. A good example of this is when you run a background thread to download something as result of an event. You only want to transition into the new state after the download is complete. You can return False from your onleave\_state\_ handler and the state machine will be put on hold until you are ready to trigger the transition using the transition() method. fysom-2.1.2/CHANGELOG0000644011710100017500000000411712564077734014660 0ustar mriehladmins00000000000000Changelog for fysom -------------------- * v2.1.2 Add special symbol for dst state equals to src state. Pull request by [irpab](https://github.com/irpab) * v2.1.0 In cases where a class has a state machine instance as a member and uses methods for callbacks, the dependencies between the parent class and the child state machine created cycles in the garbage collector. In order to break these cycles, we modified the state machine to store weak references to the method. For non-method functions, we can store these safely since the function itself should not hold a reference to the state machine. Pull request by [sjlongland](https://github.com/sjlongland) * v2.0.1 State re-entry or 'reflexive' callbacks (onreenter_state) called when the start and end state of a transition are the same. This is different to the change in v1.1.0 as the new callback type is for a particular state and not a particular event. This allows individual callbacks for reflexive transitions rather than event callbacks with conditional branches to handle both reflexive and non-reflexive transitions. Pull request by [@mattjml](https://github.com/mattjml) * v2.0.0 BREAKING CHANGE - Canceling an event by returning `False` from the onbefore callback will now raise `fysom.Canceled` instead of just ignoring the event. * v1.1.2 Extend Fysom constructor to allow for terser FSM specifications. Pull request by [@astanin](https://github.com/astanin). * v1.1.1 Resolved problems with installation on windows. * v1.1.0 Event callbacks (onbefore_event_|onafter_event_|on_event_) will now trigger when the state does not change. Previously those callbacks would only fire in the case of a state change, so events keeping the FSM in the same state would not fire callbacks at all. * v1.0.19 The `trigger` method now accepts any positional arguments and keyword arguments, and passes them to the underlying event method. Pull request by [@poundifdef](https://github.com/poundifdef). * v1.0.18 From now on, a changelog will be included. Furthermore, all PyPI releases will be GPG signed. fysom-2.1.2/fysom/0000755011710100017500000000000012564100143014555 5ustar mriehladmins00000000000000fysom-2.1.2/fysom/__init__.py0000644011710100017500000003200712564077734016713 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import collections import weakref import types import sys __author__ = 'Mansour Behabadi' __copyright__ = 'Copyright 2011, Mansour Behabadi and Jake Gordon' __credits__ = ['Mansour Behabadi', 'Jake Gordon'] __license__ = 'MIT' __version__ = '2.1.2' __maintainer__ = 'Mansour Behabadi' __email__ = 'mansour@oxplot.com' WILDCARD = '*' SAME_DST = '=' class FysomError(Exception): ''' Raised whenever an unexpected event gets triggered. ''' pass class Canceled(FysomError): ''' Raised when an event is canceled due to the onbeforeevent handler returning False ''' def _weak_callback(func): ''' Store a weak reference to a callback or method. ''' if isinstance(func, types.MethodType): # Don't hold a reference to the object, otherwise we might create # a cycle. # Reference: http://stackoverflow.com/a/6975682 # Tell coveralls to not cover this if block, as the Python 2.x case # doesn't test the 3.x code and vice versa. if sys.version_info[0] < 3: # pragma: no cover # Python 2.x case obj_ref = weakref.ref(func.im_self) func_ref = weakref.ref(func.im_func) else: # pragma: no cover # Python 3.x case obj_ref = weakref.ref(func.__self__) func_ref = weakref.ref(func.__func__) func = None def _callback(*args, **kwargs): obj = obj_ref() func = func_ref() if (obj is None) or (func is None): return return func(obj, *args, **kwargs) return _callback else: # We should be safe enough holding callback functions ourselves. return func class Fysom(object): ''' Wraps the complete finite state machine operations. ''' def __init__(self, cfg={}, initial=None, events=None, callbacks=None, final=None): ''' Construct a Finite State Machine. Arguments: cfg finite state machine specification, a dictionary with keys 'initial', 'events', 'callbacks', 'final' initial initial state events a list of dictionaries (keys: 'name', 'src', 'dst') or a list tuples (event name, source state or states, destination state or states) callbacks a dictionary mapping callback names to functions final a state of the FSM where its is_finished() method returns True Named arguments override configuration dictionary. Example: >>> fsm = Fysom(events=[('tic', 'a', 'b'), ('toc', 'b', 'a')], initial='a') >>> fsm.current 'a' >>> fsm.tic() >>> fsm.current 'b' >>> fsm.toc() >>> fsm.current 'a' ''' cfg = dict(cfg) # override cfg with named arguments if "events" not in cfg: cfg["events"] = [] if "callbacks" not in cfg: cfg["callbacks"] = {} if initial: cfg["initial"] = initial if final: cfg["final"] = final if events: cfg["events"].extend(list(events)) if callbacks: cfg["callbacks"].update(dict(callbacks)) # convert 3-tuples in the event specification to dicts events_dicts = [] for e in cfg["events"]: if isinstance(e, collections.Mapping): events_dicts.append(e) elif hasattr(e, "__iter__"): name, src, dst = list(e)[:3] events_dicts.append({"name": name, "src": src, "dst": dst}) cfg["events"] = events_dicts self._apply(cfg) def isstate(self, state): ''' Returns if the given state is the current state. ''' return self.current == state def can(self, event): ''' Returns if the given event be fired in the current machine state. ''' return ( event in self._map and ((self.current in self._map[event]) or WILDCARD in self._map[event]) and not hasattr(self, 'transition')) def cannot(self, event): ''' Returns if the given event cannot be fired in the current state. ''' return not self.can(event) def is_finished(self): ''' Returns if the state machine is in its final state. ''' return self._final and (self.current == self._final) def _apply(self, cfg): ''' Does the heavy lifting of machine construction. More notably: >> Sets up the initial and finals states. >> Sets the event methods and callbacks into the same object namespace. >> Prepares the event to state transitions map. ''' init = cfg['initial'] if 'initial' in cfg else None if self._is_base_string(init): init = {'state': init} self._final = cfg['final'] if 'final' in cfg else None events = cfg['events'] if 'events' in cfg else [] callbacks = cfg['callbacks'] if 'callbacks' in cfg else {} tmap = {} self._map = tmap def add(e): ''' Adds the event into the machine map. ''' if 'src' in e: src = [e['src']] if self._is_base_string( e['src']) else e['src'] else: src = [WILDCARD] if e['name'] not in tmap: tmap[e['name']] = {} for s in src: tmap[e['name']][s] = e['dst'] # Consider initial state as any other state that can have transition from none to # initial value on occurance of startup / init event ( if specified). if init: if 'event' not in init: init['event'] = 'startup' add({'name': init['event'], 'src': 'none', 'dst': init['state']}) for e in events: add(e) # For all the events as present in machine map, construct the event # handler. for name in tmap: setattr(self, name, self._build_event(name)) # For all the callbacks, register them into the current object # namespace. for name in callbacks: setattr(self, name, _weak_callback(callbacks[name])) self.current = 'none' # If initialization need not be deferred, trigger the event for # transition to initial state. if init and 'defer' not in init: getattr(self, init['event'])() def _build_event(self, event): ''' For every event in the state machine, prepares the event handler and registers the same into current object namespace. ''' def fn(*args, **kwargs): if hasattr(self, 'transition'): raise FysomError( "event %s inappropriate because previous transition did not complete" % event) # Check if this event can be triggered in the current state. if not self.can(event): raise FysomError( "event %s inappropriate in current state %s" % (event, self.current)) # On event occurence, source will always be the current state. src = self.current # Finds the destination state, after this event is completed. dst = ((src in self._map[event] and self._map[event][src]) or WILDCARD in self._map[event] and self._map[event][WILDCARD]) if dst == SAME_DST: dst = src # Prepares the object with all the meta data to be passed to # callbacks. class _e_obj(object): pass e = _e_obj() e.fsm, e.event, e.src, e.dst = self, event, src, dst for k in kwargs: setattr(e, k, kwargs[k]) setattr(e, 'args', args) # Try to trigger the before event, unless it gets canceled. if self._before_event(e) is False: raise Canceled( "Cannot trigger event {0} because the onbefore{0} handler returns False".format(e.event)) # Wraps the activities that must constitute a single successful # transaction. if self.current != dst: def _tran(): delattr(self, 'transition') self.current = dst self._enter_state(e) self._change_state(e) self._after_event(e) self.transition = _tran # Hook to perform asynchronous transition. if self._leave_state(e) is not False: self.transition() else: self._reenter_state(e) self._after_event(e) fn.__name__ = event fn.__doc__ = ("Event handler for an {event} event. This event can be " + "fired if the machine is in {states} states.".format( event=event, states=self._map[event].keys())) return fn def _before_event(self, e): ''' Checks to see if the callback is registered before this event can be triggered. ''' fnname = 'onbefore' + e.event if hasattr(self, fnname): return getattr(self, fnname)(e) def _after_event(self, e): ''' Checks to see if the callback is registered for, after this event is completed. ''' for fnname in ['onafter' + e.event, 'on' + e.event]: if hasattr(self, fnname): return getattr(self, fnname)(e) def _leave_state(self, e): ''' Checks to see if the machine can leave the current state and perform the transition. This is helpful if the asynchronous job needs to be completed before the machine can leave the current state. ''' fnname = 'onleave' + e.src if hasattr(self, fnname): return getattr(self, fnname)(e) def _enter_state(self, e): ''' Executes the callback for onenter_state_ or on_state_. ''' for fnname in ['onenter' + e.dst, 'on' + e.dst]: if hasattr(self, fnname): return getattr(self, fnname)(e) def _reenter_state(self, e): ''' Executes the callback for onreenter_state_. This allows callbacks following reflexive transitions (i.e. where src == dst) ''' fnname = 'onreenter' + e.dst if hasattr(self, fnname): return getattr(self, fnname)(e) def _change_state(self, e): ''' A general change state callback. This gets triggered at the time of state transition. ''' fnname = 'onchangestate' if hasattr(self, fnname): return getattr(self, fnname)(e) def _is_base_string(self, object): # pragma: no cover ''' Returns if the object is an instance of basestring. ''' try: return isinstance(object, basestring) except NameError: return isinstance(object, str) def trigger(self, event, *args, **kwargs): ''' Triggers the given event. The event can be triggered by calling the event handler directly, for ex: fsm.eat() but this method will come in handy if the event is determined dynamically and you have the event name to trigger as a string. ''' if not hasattr(self, event): raise FysomError( "There isn't any event registered as %s" % event) return getattr(self, event)(*args, **kwargs) fysom-2.1.2/test/0000755011710100017500000000000012564100143014377 5ustar mriehladmins00000000000000fysom-2.1.2/test/test_state.py0000644011710100017500000001025012564077734017151 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom, FysomError class FysomStateTests(unittest.TestCase): def setUp(self): self.fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'}, {'name': 'warm', 'src': 'green', 'dst': 'blue'} ] }) def test_is_state_should_succeed_for_initial_state(self): self.assertTrue(self.fsm.isstate('green')) def test_identity_transition_should_not_be_allowed_by_default(self): self.assertFalse(self.fsm.can('clear')) self.assertTrue(self.fsm.cannot('clear')) def test_configured_transition_should_work(self): self.assertTrue(self.fsm.can('warn')) def test_transition_should_change_state(self): self.fsm.warn() self.assertTrue(self.fsm.isstate('yellow')) def test_should_raise_exception_when_state_transition_is_not_allowed(self): self.assertRaises(FysomError, self.fsm.panic) self.assertRaises(FysomError, self.fsm.calm) self.assertRaises(FysomError, self.fsm.clear) def test_event_handler_has_name_and_docstring(self): self.assertEqual(self.fsm.warm.__name__, "warm", "Event handlers do not have appropriate name.") self.assertNotEqual(self.fsm.warm.__name__, None, "Docstring for event handler is None!") def test_trigger_should_trigger_the_event_handler(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") self.fsm.trigger("warm") self.assertRaises(FysomError, self.fsm.trigger, "unknown_event") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.") def test_trigger_should_trigger_the_event_handler_with_args(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") def onblue(event): self.assertEqual(event.args, ("any-positional-argument",)) self.fsm.onblue = onblue self.fsm.trigger("warm", "any-positional-argument") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.") def test_trigger_should_trigger_the_event_handler_with_kwargs(self): self.assertEqual(self.fsm.current, "green", "The initial state isn't the expected state.") def onblue(event): self.assertEqual(event.keyword_argument, "any-value") self.fsm.onblue = onblue self.fsm.trigger("warm", keyword_argument="any-value") self.assertEqual(self.fsm.current, "blue", "The initial state isn't the expected state.") fysom-2.1.2/test/test_callbacks.py0000644011710100017500000002402512564077734017755 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom, Canceled class FysomRepeatedBeforeEventCallbackTests(unittest.TestCase): def setUp(self): self.fired = [] def record_event(event): self.fired.append(event.msg) return 42 self.fsm = Fysom({ 'initial': 'notcalled', 'events': [ {'name': 'multiple', 'src': 'notcalled', 'dst': 'called'}, {'name': 'multiple', 'src': 'called', 'dst': 'called'}, ], 'callbacks': { 'onbeforemultiple': record_event } }) def test_should_fire_onbefore_event_repeatedly(self): self.fsm.multiple(msg="first") self.fsm.multiple(msg="second") self.assertEqual(self.fired, ["first", "second"]) class FysomRepeatedAfterEventCallbackTests(unittest.TestCase): def setUp(self): self.fired = [] def record_event(event): self.fired.append(event.msg) return 42 self.fsm = Fysom({ 'initial': 'notcalled', 'events': [ {'name': 'multiple', 'src': 'notcalled', 'dst': 'called'}, {'name': 'multiple', 'src': 'called', 'dst': 'called'}, ], 'callbacks': { 'onaftermultiple': record_event } }) def test_should_fire_onafter_event_repeatedly(self): self.fsm.multiple(msg="first") self.fsm.multiple(msg="second") self.assertEqual(self.fired, ["first", "second"]) class FysomCallbackTests(unittest.TestCase): def before_foo(self, e): self.before_foo_event = e self.fired_callbacks.append('before_foo') def before_bar(self, e): self.before_bar_event = e self.fired_callbacks.append('before_bar') def before_wait(self, e): self.before_wait_event = e self.fired_callbacks.append('before_wait') return False def on_foo(self, e): self.foo_event = e self.fired_callbacks.append('after_foo') def on_bar(self, e): self.bar_event = e self.fired_callbacks.append('after_bar') def on_baz(self, e): raise ValueError('Baz-inga!') def on_enter_fooed(self, e): self.enter_fooed_event = e def on_enter_bared(self, e): self.enter_bared_event = e def on_reenter_fooed(self, e): self.reenter_fooed_event = e def on_reenter_bared(self, e): self.reenter_bared_event = e def on_leave_sleeping(self, e): self.leave_sleeping_event = e def on_leave_fooed(self, e): self.leave_fooed_event = e def setUp(self): self.fired_callbacks = [] self.fsm = Fysom({ 'initial': 'sleeping', 'events': [ {'name': 'foo', 'src': 'sleeping', 'dst': 'fooed'}, {'name': 'foo', 'src': 'fooed', 'dst': 'fooed'}, {'name': 'bar', 'src': 'fooed', 'dst': 'bared'}, {'name': 'bar', 'src': 'bared', 'dst': 'bared'}, {'name': 'baz', 'src': 'bared', 'dst': 'bazed'}, {'name': 'wait', 'src': 'sleeping', 'dst': 'waiting'} ], 'callbacks': { 'onfoo': self.on_foo, 'onbar': self.on_bar, 'onbaz': self.on_baz, 'onbeforefoo': self.before_foo, 'onbeforebar': self.before_bar, 'onbeforewait': self.before_wait, 'onenterfooed': self.on_enter_fooed, 'onenterbared': self.on_enter_bared, 'onreenterfooed': self.on_reenter_fooed, 'onreenterbared': self.on_reenter_bared, 'onleavesleeping': self.on_leave_sleeping, 'onleavefooed': self.on_leave_fooed } }) def test_onafter_event_callbacks_should_fire_with_keyword_arguments_when_events_occur(self): self.fsm.foo(attribute='test') self.assertTrue( hasattr(self, 'foo_event'), 'Callback on_foo did not fire.') self.assertTrue(self.foo_event is not None) self.assertEqual(self.foo_event.attribute, 'test') self.fsm.bar(id=123) self.assertTrue( hasattr(self, 'bar_event'), 'Callback on_bar did not fire.') self.assertTrue(self.bar_event is not None) self.assertEqual(self.bar_event.id, 123) def test_onafter_event_callbacks_raising_exceptions_should_not_be_eaten(self): self.fsm.foo() self.fsm.bar() self.assertRaises(ValueError, self.fsm.baz) def test_onbefore_event_callbacks_should_fire_before_onafter_callbacks_with_keyword_arguments_when_events_occur( self): self.fsm.foo(attribute='test') self.assertTrue( hasattr(self, 'before_foo_event'), 'Callback onbeforefoo did not fire.') self.assertTrue(self.before_foo_event is not None) self.assertEqual(self.before_foo_event.attribute, 'test') self.fsm.bar(id=123) self.assertTrue( hasattr(self, 'before_bar_event'), 'Callback onbeforebar did not fire.') self.assertTrue(self.before_bar_event is not None) self.assertEqual(self.before_bar_event.id, 123) self.assertEqual( ['before_foo', 'after_foo', 'before_bar', 'after_bar'], self.fired_callbacks) def test_fsm_cancel_transition_when_onbefore_event_callbacks_return_false(self): self.assertRaises(Canceled, self.fsm.wait) self.assertEqual(self.fsm.current, 'sleeping') def test_onenter_state_callbacks_should_fire_with_keyword_arguments_when_state_transitions_occur(self): self.fsm.foo(attribute='test') self.assertTrue( hasattr(self, 'enter_fooed_event'), 'Callback onenterfooed did not fire.') self.assertTrue(self.enter_fooed_event is not None) self.assertEqual(self.enter_fooed_event.attribute, 'test') self.fsm.bar(id=123) self.assertTrue( hasattr(self, 'enter_bared_event'), 'Callback onenterbared did not fire.') self.assertTrue(self.enter_bared_event is not None) self.assertEqual(self.enter_bared_event.id, 123) def test_onreenter_state_callbacks_should_fire_with_keyword_arguments_when_state_transitions_occur(self): self.fsm.foo(attribute='testfail') self.fsm.foo(attribute='testpass') self.assertTrue( hasattr(self, 'reenter_fooed_event'), 'Callback onreenterfooed did not fire.') self.assertTrue(self.reenter_fooed_event is not None) self.assertEqual(self.reenter_fooed_event.attribute, 'testpass') self.fsm.bar(id=1234) self.fsm.bar(id=4321) self.assertTrue( hasattr(self, 'reenter_bared_event'), 'Callback onreenterbared did not fire.') self.assertTrue(self.reenter_bared_event is not None) self.assertEqual(self.reenter_bared_event.id, 4321) def test_onleave_state_callbacks_should_fire_with_keyword_arguments_when_state_transitions_occur(self): self.fsm.foo(attribute='test') self.assertTrue(hasattr(self, 'leave_sleeping_event'), 'Callback onleavesleeping did not fire.') self.assertTrue(self.leave_sleeping_event is not None) self.assertEqual(self.leave_sleeping_event.attribute, 'test') self.fsm.bar(id=123) self.assertTrue( hasattr(self, 'leave_fooed_event'), 'Callback onleavefooed did not fire.') self.assertTrue(self.leave_fooed_event is not None) self.assertEqual(self.leave_fooed_event.id, 123) def test_onchangestate_should_fire_for_all_state_changes(self): def on_change_state(e): self.current_event = e fsm = Fysom({ 'initial': 'foo', 'events': [ {'name': 'footobar', 'src': 'foo', 'dst': 'bar'}, {'name': 'bartobaz', 'src': 'bar', 'dst': 'baz'}, ], 'callbacks': { 'onchangestate': on_change_state } }) fsm.footobar(id=123) self.assertEqual(self.current_event.event, 'footobar') self.assertEqual(self.current_event.src, 'foo') self.assertEqual(self.current_event.dst, 'bar') self.assertEqual(self.current_event.id, 123) self.assertTrue(self.current_event.fsm is fsm) fsm.bartobaz('positional', named_attribute='test') self.assertEqual(self.current_event.event, 'bartobaz') self.assertEqual(self.current_event.src, 'bar') self.assertEqual(self.current_event.dst, 'baz') self.assertEqual(self.current_event.named_attribute, 'test') self.assertEqual(self.current_event.args[0], 'positional') self.assertTrue(self.current_event.fsm is fsm) fysom-2.1.2/test/test_many_to_many.py0000644011710100017500000000605112564077734020527 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest import copy from fysom import Fysom class FysomManyToManyTransitionTests(unittest.TestCase): fsm_descr = { 'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'rest', 'src': ['hungry', 'satisfied', 'full', 'sick'], 'dst': 'hungry'} ] } def _get_descr(self, initial=None): mycopy = copy.deepcopy(self.fsm_descr) if initial: mycopy['initial'] = initial return mycopy def test_rest_should_always_transition_to_hungry_state(self): fsm = Fysom(self.fsm_descr) fsm.rest() self.assertEquals(fsm.current, 'hungry') fsm = Fysom(self._get_descr('satisfied')) fsm.rest() self.assertEquals(fsm.current, 'hungry') fsm = Fysom(self._get_descr('full')) fsm.rest() self.assertEquals(fsm.current, 'hungry') fsm = Fysom(self._get_descr('sick')) fsm.rest() self.assertEquals(fsm.current, 'hungry') def test_eat_should_transition_to_satisfied_when_hungry(self): fsm = Fysom(self._get_descr('hungry')) fsm.eat() self.assertEqual(fsm.current, 'satisfied') def test_eat_should_transition_to_full_when_satisfied(self): fsm = Fysom(self._get_descr('satisfied')) fsm.eat() self.assertEqual(fsm.current, 'full') def test_eat_should_transition_to_sick_when_full(self): fsm = Fysom(self._get_descr('full')) fsm.eat() self.assertEqual(fsm.current, 'sick') fysom-2.1.2/test/test_finished_state.py0000644011710100017500000000524612564077734021033 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom class FysomFinishedStateTests(unittest.TestCase): def test_it_should_indicate_whether_fsm_in_finished_state(self): fsm = Fysom({ 'initial': 'green', 'final': 'red', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertFalse(fsm.is_finished()) fsm.warn() self.assertFalse(fsm.is_finished()) fsm.panic() self.assertTrue(fsm.is_finished()) def test_never_finished_if_final_is_unspecified(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertFalse(fsm.is_finished()) fsm.warn() self.assertFalse(fsm.is_finished()) fsm.panic() self.assertFalse(fsm.is_finished()) fysom-2.1.2/test/test_initialization.py0000644011710100017500000001247212564077734021070 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom class FysomInitializationTests(unittest.TestCase): def test_should_have_no_state_when_no_initial_state_is_given(self): fsm = Fysom({ 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertEqual(fsm.current, 'none') def test_initial_state_should_be_green_when_configured(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) self.assertEqual(fsm.current, 'green') def test_initial_state_should_work_with_different_event_name(self): fsm = Fysom({ 'initial': {'state': 'green', 'event': 'init'}, 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}, ] }) self.assertEquals(fsm.current, 'green') def test_deferred_initial_state_should_be_none_then_state(self): fsm = Fysom({ 'initial': {'state': 'green', 'event': 'init', 'defer': True}, 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}, ] }) self.assertEqual(fsm.current, 'none') fsm.init() self.assertEqual(fsm.current, 'green') def test_tuples_as_trasition_spec(self): fsm = Fysom({ 'initial': 'green', 'events': [ # freely mix dicts and tuples {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, ('panic', 'yellow', 'red'), ('calm', 'red', 'yellow'), {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ] }) fsm.warn() fsm.panic() self.assertEqual(fsm.current, 'red') fsm.calm() fsm.clear() self.assertEqual(fsm.current, 'green') def test_kwargs_override_cfg(self): fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'panic', 'src': 'green', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'green'}, ]}, # override initial state and calm event initial='red', events=[('calm', 'red', 'black')]) self.assertEqual(fsm.current, "red") fsm.calm() self.assertEqual(fsm.current, "black") def test_init_kwargs_only(self): fsm = Fysom(initial='green', events=[('panic', 'green', 'red'), ('calm', 'red', 'green')]) self.assertEqual(fsm.current, "green") fsm.panic() self.assertEqual(fsm.current, "red") fsm.calm() self.assertEqual(fsm.current, "green") def test_final_kwarg(self): fsm = Fysom(initial='eternity', final='eternity') self.assertEqual(fsm.current, 'eternity') self.assertEqual(fsm.is_finished(), True) def test_callbacks_kwarg(self): history = [] def ontic(e): history.append('tic') def ontoc(e): history.append('toc') fsm = Fysom(initial='left', events=[('tic', 'left', 'right'), ('toc', 'right', 'left')], callbacks={'ontic': ontic, 'ontoc': ontoc}) fsm.tic() fsm.toc() fsm.tic() fsm.toc() self.assertEqual(history, ['tic', 'toc', 'tic', 'toc']) fysom-2.1.2/test/test_garbage_collection.py0000644011710100017500000001056512564077734021645 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest import gc from fysom import Fysom class FysomGarbageCollectionTests(unittest.TestCase): def test_should_not_create_circular_ref(self): class MyTestObject(object): def __init__(self): self._states = [] self._fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ], 'callbacks': { 'ongreen': self._on_green, 'onyellow': self._on_yellow, 'onred': self._on_red } }) def warn(self): self._fsm.warn() def panic(self): self._fsm.panic() def calm(self): self._fsm.calm() def clear(self): self._fsm.clear() def _on_green(self, *args, **kwargs): self._states.append('green') def _on_yellow(self, *args, **kwargs): self._states.append('yellow') def _on_red(self, *args, **kwargs): self._states.append('red') obj = MyTestObject() obj.warn() obj.clear() del obj self.assertEqual(list(filter(lambda o: isinstance(o, MyTestObject), gc.get_objects())), []) def test_gc_should_not_break_callback(self): class MyTestObject(object): def __init__(self): self._states = [] self._fsm = None def warn(self): self._fsm.warn() def panic(self): self._fsm.panic() def calm(self): self._fsm.calm() def clear(self): self._fsm.clear() def _on_green(self, *args, **kwargs): self._states.append('green') def _on_yellow(self, *args, **kwargs): self._states.append('yellow') def _on_red(self, *args, **kwargs): self._states.append('red') obj = MyTestObject() fsm = Fysom({ 'initial': 'green', 'events': [ {'name': 'warn', 'src': 'green', 'dst': 'yellow'}, {'name': 'panic', 'src': 'yellow', 'dst': 'red'}, {'name': 'calm', 'src': 'red', 'dst': 'yellow'}, {'name': 'clear', 'src': 'yellow', 'dst': 'green'} ], 'callbacks': { 'ongreen': obj._on_green, 'onyellow': obj._on_yellow, 'onred': obj._on_red } }) obj._fsm = fsm obj.warn() obj.clear() del obj fsm.warn() fysom-2.1.2/test/test_wildcard.py0000644011710100017500000000503012564077734017622 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # from fysom import Fysom from test_many_to_many import FysomManyToManyTransitionTests class FysomWildcardTransitionTests(FysomManyToManyTransitionTests): fsm_descr = { 'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'rest', 'src': '*', 'dst': 'hungry'} ] } def test_if_src_not_specified_then_is_wildcard(self): fsm = Fysom({ 'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'rest', 'dst': 'hungry'} ] }) fsm.eat() self.assertEqual(fsm.current, 'satisfied') fsm.rest() self.assertEqual(fsm.current, 'hungry') fsm.eat() fsm.eat() self.assertEqual(fsm.current, 'full') fsm.rest() self.assertEqual(fsm.current, 'hungry') fysom-2.1.2/test/test_same_dst.py0000644011710100017500000000615412564077734017640 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom class FysomSameDstTransitionTests(unittest.TestCase): def test_if_src_not_specified_then_is_wildcard(self): fsm = Fysom({ 'initial': 'hungry', 'events': [ {'name': 'eat', 'src': 'hungry', 'dst': 'satisfied'}, {'name': 'eat', 'src': 'satisfied', 'dst': 'full'}, {'name': 'eat', 'src': 'full', 'dst': 'sick'}, {'name': 'eat', 'src': 'sick', 'dst': '='}, {'name': 'rest', 'src': '*', 'dst': 'hungry'}, {'name': 'walk', 'src': '*', 'dst': '='}, {'name': 'run', 'src': ['hungry', 'sick'], 'dst': '='}, {'name': 'run', 'src': 'satisfied', 'dst': 'hungry'}, {'name': 'run', 'src': 'full', 'dst': 'satisfied'} ] }) fsm.walk() self.assertEqual(fsm.current, 'hungry') fsm.run() self.assertEqual(fsm.current, 'hungry') fsm.eat() self.assertEqual(fsm.current, 'satisfied') fsm.walk() self.assertEqual(fsm.current, 'satisfied') fsm.run() self.assertEqual(fsm.current, 'hungry') fsm.eat() self.assertEqual(fsm.current, 'satisfied') fsm.eat() self.assertEqual(fsm.current, 'full') fsm.walk() self.assertEqual(fsm.current, 'full') fsm.eat() self.assertEqual(fsm.current, 'sick') fsm.walk() self.assertEqual(fsm.current, 'sick') fsm.run() self.assertEqual(fsm.current, 'sick') fsm.rest() self.assertEqual(fsm.current, 'hungry') fsm.eat() self.assertEqual(fsm.current, 'satisfied') fsm.run() self.assertEqual(fsm.current, 'hungry') fysom-2.1.2/test/test_async_state_transition.py0000644011710100017500000000571612564077734022633 0ustar mriehladmins00000000000000# coding=utf-8 # # fysom - pYthOn Finite State Machine - this is a port of Jake # Gordon's javascript-state-machine to python # https://github.com/jakesgordon/javascript-state-machine # # Copyright (C) 2011 Mansour Behabadi , Jake Gordon # and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # import unittest from fysom import Fysom, FysomError class FysomAsynchronousStateTransitionTests(unittest.TestCase): def on_leave_foo(self, e): self.leave_foo_event = e return False def on_enter_bar(self, e): self.on_enter_bar_fired = True self.on_enter_bar_event = e def setUp(self): self.on_enter_bar_fired = False self.fsm = Fysom({ 'initial': 'foo', 'events': [ {'name': 'footobar', 'src': 'foo', 'dst': 'bar'}, {'name': 'bartobar', 'src': 'bar', 'dst': 'bar'}, ], 'callbacks': { 'onleavefoo': self.on_leave_foo, 'onenterbar': self.on_enter_bar, } }) def test_fsm_should_be_put_on_hold_when_onleave_state_returns_false(self): self.fsm.footobar(id=123) self.assertEqual(self.fsm.current, 'foo') self.assertTrue(hasattr(self, 'leave_foo_event'), 'Callback onleavefoo did not fire.') self.assertTrue(self.leave_foo_event is not None) self.assertEqual(self.leave_foo_event.id, 123) self.fsm.transition() self.assertEqual(self.fsm.current, 'bar') def test_onenter_state_should_not_fire_when_fsm_is_put_on_hold(self): self.fsm.footobar(id=123) self.assertFalse(self.on_enter_bar_fired) self.fsm.transition() self.assertTrue(self.on_enter_bar_fired) def test_should_raise_exception_upon_further_transitions_when_fsm_is_on_hold(self): self.fsm.footobar(id=123) self.assertRaises(FysomError, self.fsm.bartobar) fysom-2.1.2/setup.py0000755011710100017500000000204712564077734015163 0ustar mriehladmins00000000000000#!/usr/bin/env python from setuptools import setup if __name__ == '__main__': setup( name = 'fysom', version = '2.1.2', description = '''pYthOn Finite State Machine''', long_description = '''''', author = "Mansour Behabadi, Jake Gordon, Maximilien Riehl, Stefano", author_email = "mansour@oxplot.com, jake@codeincomplete.com, maximilien.riehl@gmail.com, ", license = 'MIT', url = 'https://github.com/mriehl/fysom', scripts = [], packages = ['fysom'], py_modules = [], classifiers = ['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Scientific/Engineering'], entry_points={ 'console_scripts': [] }, # data files # package data zip_safe=True ) fysom-2.1.2/PKG-INFO0000644011710100017500000000122312564100143014513 0ustar mriehladmins00000000000000Metadata-Version: 1.1 Name: fysom Version: 2.1.2 Summary: pYthOn Finite State Machine Home-page: https://github.com/mriehl/fysom Author: Mansour Behabadi, Jake Gordon, Maximilien Riehl, Stefano Author-email: mansour@oxplot.com, jake@codeincomplete.com, maximilien.riehl@gmail.com, License: MIT Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Scientific/Engineering fysom-2.1.2/MANIFEST.in0000644011710100017500000000004112564077734015174 0ustar mriehladmins00000000000000include README include CHANGELOG fysom-2.1.2/setup.cfg0000644011710100017500000000024612564100143015243 0ustar mriehladmins00000000000000[bdist_rpm] packager = Maximilien Riehl requires = python >= 2.6 release = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0