Skip to main content

Basic Finite State Machine (FSM) tools.

Project description

Basic Finite State Machine (FSM) tools.

Latest release 20240721: FSM: new fsm_print_state_diagram method to write the state diagram out in various formats.

Class CancellationError(FSMError)

Subclass of FSMError Raised when trying to make use of an FSM which is cancelled.

For example, this is raised by a cs.result.Result when accessing .result or .exc_info after cancellation.

CancellationError.__init__(self, message=None, *, fsm=None, **kw): Initialise the CancellationError.

The optional message parameter (default "cancelled") is set as the message attribute. Other keyword parameters set their matching attributes.

Class FSM(cs.gvutils.DOTNodeMixin)

Base class for a finite state machine (FSM).

The allowed states and transitions are defined by the class attribute FSM_TRANSITIONS, a mapping of state->event->new_state.

Each instance has the following attributes:

  • fsm_state: the current state value.
  • fsm_history: an optional iterable of FSMTransitionEvent state transitions recorded by the fsm_event method. Usually this would be None (the default) or a list.
<title>FSM State Diagram</title>
FSM State Diagram

FSM.__init__(self, state=None, *, history=None, lock=None, transitions=None): Initialise the FSM from:

  • state: optional positional parameter for the initial state, default self.FSM_DEFAULT_STATE or the first key from self.FSM_TRANSITIONS
  • history: an optional object to record state transition history, default None; if not None this should be an iterable object with a .append(entry) method such as a list.
  • lock: an optional mutex to control access; if presupplied and shared with the caller it should probably be an RLock; the default is a Lock, which is enough for FSM private use
  • transitions: optional state->event->state mapping; if provided, this will override the class FSM_TRANSITIONS mapping

Note that the FSM base class does not provide a FSM_DEFAULT_STATE attribute; a default state value of None will leave .fsm_state unset.

This behaviour is is chosen mostly to support subclasses with unusual behaviour, particularly Django's Model class whose refresh_from_db method seems to not refresh fields which already exist, and setting .fsm_state from a FSM_DEFAULT_STATE class attribute thus breaks this method. Subclasses of this class and Model should not provide a FSM_DEFAULT_STATE attribute, instead relying on the field definition to provide this default in the usual way.

FSM.__getattr__(self, attr): Provide the following attributes:

  • present the state names as attributes, for example: self.PENDING=='PENDING' if there is a 'PENDING' state
  • present is_statename as a Boolean testing whether self.fsm_state==statename.upper()
  • a callable calling self.fsm_event(attr) if attr is an event name for the current state Fall back to the superclass __getattr__.

FSM.dot_node_palette_key: Default palette index is self.fsm_state, overriding DOTNodeMixin.dot_node_palette_key.

FSM.fsm_as_svg(self, layout=None, history_style=None, **dot_kw) -> str: Render the state transition diagram as SVG.

FSM.fsm_callback(self, state, callback): Register a callback to be called immediately on transition to state as callback(self,FSMEventTransition). The special state value FSM.FSM_ANY_STATE may be supplied to register a callback which fires for every state transition.

>>> fsm = FSM('state1',transitions={
...   'state1':{'ev_a':'state2'},
...   'state2':{'ev_b':'state1'},
... })
>>> fsm.fsm_callback('state2',lambda task, transition: print(task, transition))
>>> fsm.fsm_callback(FSM.FSM_ANY_STATE,lambda task, transition: print("ANY", task, transition))
>>> fsm.ev_a(foo=3) # doctest: +ELLIPSIS
ANY FSM:state2 FSMTransitionEvent(old_state='state1', new_state='state2', event='ev_a', when=..., extra={'foo': 3})
FSM:state2 FSMTransitionEvent(old_state='state1', new_state='state2', event='ev_a', when=..., extra={'foo': 3})
'state2'
>>> fsm.ev_b(foo=4) # doctest: +ELLIPSIS
ANY FSM:state1 FSMTransitionEvent(old_state='state2', new_state='state1', event='ev_b', when=..., extra={'foo': 4})
'state1'

FSM.fsm_callback_discard(self, state, callback): Deregister a callback for state.

FSM.fsm_dot: A DOT syntax description of the state diagram in the current state.

FSM.fsm_event(self, event, **extra): Transition the FSM from the current state to a new state based on event. Call any callbacks associated with the new state. Returns the new state.

Optional information may be passed as keyword arguments.

A transition instance of FSMTransitionEvent is created with the following attributes:

  • old_state: the state when fsm_event was called
  • new_state: the new state
  • event: the event
  • when: a UNIX timestamp from time.time()
  • extra: a dict with the extra information

If self.fsm_history is not None, transition is appended to it.

If there are callbacks for new_state or FSM.FSM_ANY_STATE, call each callback as callback(self,transition).

Important note: the callbacks are run in series in the current Thread. If you need to dispatch a long running activity from a state transtion, the callback should still return promptly.

FSM.fsm_event_is_allowed(self, event): Test whether event is permitted in the current state. This can be handy as a pretest.

FSM.fsm_events: Return a list of the events valid for the current state.

FSM.fsm_history: History property wrapping private attribute. This aids subclassing where the history is not a local attribute.

FSM.fsm_print(self, file=None, fmt=None, layout=None, **dot_kw): Print the state transition diagram to file, default sys.stdout, in format fmt using the engine specified by layout, default 'dot'. This is a wrapper for cs.gvutils.gvprint.

FSM.fsm_print_state_diagram(file=None, *, fmt=None, graph_name=None, history=None, history_style=None, state=None, transitions=None, **gvprint_kw): Print the state diagram via cs.gvutils.gvprint.

The DOT syntax graph description is computed with FSM.fsm_state_diagram_as_dot and the graph_name, history, history_style, state and transitions parameters are passed through to this.

If fmt is specified as dot then the DOT and any remaining keyword arguments are passed to print().

Otherwise any remaining keyword paramaeters are passed to gvprint.

FSM.fsm_state_diagram_as_dot(transitions=None, *, sep='\n', state=None, graph_name=None, history=None, history_style=None) -> str: Compute a DOT syntax graph description of the state diagram.

Parameters:

  • transitions: optional mapping of state->event->state, default cls.FSM_TRANSITIONS
  • state: optional current state name, a key of
  • sep: optional separator between "lines", default '\n'
  • graph_name: optional name for the graph, default the class name
  • history: optional event transition history
  • history_style: optional style mapping for event transition history, used to style edges which have been traversed

FSM.fsm_svg: The state transition diagram as SVG.

FSM.fsm_transitions_as_dot(self, transitions=None, **diagram_kw) -> str: Compute a DOT syntax graph description of the state diagram.

Parameters:

  • transitions: optional mapping of state->event->state, default self.FSM_TRANSITIONS
  • sep: optional separator between "lines", default '\n'
  • graph_name: optional name for the graph, default the class name
  • history_style: optional style mapping for event transition history, used to style edges which have been traversed

Class FSMError(builtins.Exception)

An exception associated with an FSM.

These have a .fsm attribute storing an (optional) FSM reference supplied at initialisation.

FSMSubType = ~FSMSubType

Type variable.

The preferred way to construct a type variable is via the dedicated syntax for generic functions, classes, and type aliases::

class Sequence[T]:  # T is a TypeVar
    ...

This syntax can also be used to create bound and constrained type variables::

# S is a TypeVar bound to str
class StrSequence[S: str]:
    ...

# A is a TypeVar constrained to str or bytes
class StrOrBytesSequence[A: (str, bytes)]:
    ...

However, if desired, reusable type variables can also be constructed manually, like so::

T = TypeVar('T') # Can be anything S = TypeVar('S', bound=str) # Can be any subtype of str A = TypeVar('A', str, bytes) # Must be exactly str or bytes

Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function and type alias definitions.

The variance of type variables is inferred by type checkers when they are created through the type parameter syntax and when infer_variance=True is passed. Manually created type variables may be explicitly marked covariant or contravariant by passing covariant=True or contravariant=True. By default, manually created type variables are invariant. See PEP 484 and PEP 695 for more details.

Class FSMTransitionEvent(builtins.tuple)

FSMTransitionEvent(old_state, new_state, event, when, extra)

FSMTransitionEvent.event: Alias for field number 2

FSMTransitionEvent.extra: Alias for field number 4

FSMTransitionEvent.new_state: Alias for field number 1

FSMTransitionEvent.old_state: Alias for field number 0

FSMTransitionEvent.when: Alias for field number 3

Release Log

Release 20240721: FSM: new fsm_print_state_diagram method to write the state diagram out in various formats.

Release 20240712: FSM: make fsm_transitions_as_dot a shim for new fsm_state_diagram_as_dot class method.

Release 20240630:

  • CancellationError from cs.result to cs.fsm, and use it to catch silently FSM event callbacks which raise it.
  • Some other minor churn.

Release 20240519: FSM: default for FSM_DEFAULT_STATE is the first key from FSM_TRANSITIONS (relies on ordered dicts, so Python 3.6 onward).

Release 20240316: Fixed release upload artifacts.

Release 20240305: FSM.getattr: return None for missing self.fsm_state, happens in too-early call to str.

Release 20231020: FSM.getattr: known transition names no longerfall through to the superclass if not valid for the current state.

Release 20231018:

  • FSM.fsm_transitions_as_dot: new optional history_style parameter to style transitioned edges.
  • FSM.fsm_as_svg: plumb optional history_style parameter.

Release 20230816.3: Bump cs.gvutils requirement.

Release 20230816.2: FSM.fsm_transitions_as_dot: bugfix: the style needs "style=filled" as well as the fillcolor.

Release 20230816.1: FSM.fsm_transitions_as_dot: now an instance method so that we can colour the current state.

Release 20230816: FSM: new fsm_as_svg method and fsm_svg property.

Release 20221118:

  • FSM.init: make state optional, default from self.FSM_DEFAULT_STATE - now all args are optional.
  • FSM.init: if the state is None or not supplied, do not set .fsm_state at all; add explaination for this weird design choice.
  • FSM.getattr: only generate event methods for events with public names (no leading underscore).
  • FSM: new .fsm_history property, aiding subclassing elsewhere.
  • FSM: drop dot_node_fillcolor, now provided by DOTNodeMixin.getattr, provide dot_node_palette_key using self.fsm_state.
  • FSM.dot_node_attrs: color from self.dot_node_color.

Release 20220918: Replace callback exception warning() with exception() for the traceback.

Release 20220805.1:

  • FSM: subclass DOTNodeMixin and provide a hook for a colour palette for node fillcolors.
  • Other minor changes.

Release 20220805: Initial PyPI release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cs_fsm-20240721.tar.gz (12.5 kB view hashes)

Uploaded Source

Built Distribution

cs.fsm-20240721-py3-none-any.whl (11.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page