Basic Finite State Machine (FSM) tools.
Project description
Basic Finite State Machine (FSM) tools.
Latest 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.
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.
Method 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 ofFSMTransitionEvent
state transitions recorded by thefsm_event
method. Usually this would beNone
(the default) or alist
.
Method FSM.__init__(self, state=None, *, history=None, lock=None, transitions=None)
:
Initialise the FSM
from:
state
: optional positional parameter for the initial state, defaultself.FSM_DEFAULT_STATE
or the first key fromself.FSM_TRANSITIONS
history
: an optional object to record state transition history, defaultNone
; if notNone
this should be an iterable object with a.append(entry)
method such as alist
.lock
: an optional mutex to control access; if presupplied and shared with the caller it should probably be anRLock
; the default is aLock
, which is enough forFSM
private usetransitions
: optional state->event->state mapping; if provided, this will override the classFSM_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.
Method 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 whetherself.fsm_state==
statename.upper()
- a callable calling
self.fsm_event(attr)
ifattr
is an event name for the current state Fall back to the superclass__getattr__
.
Property FSM.dot_node_palette_key
:
Default palette index is self.fsm_state
,
overriding DOTNodeMixin.dot_node_palette_key
.
Method FSM.fsm_as_svg(self, layout=None, history_style=None, **dot_kw) -> str
:
Render the state transition diagram as SVG.
Method 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'
Method FSM.fsm_callback_discard(self, state, callback)
:
Deregister a callback for state
.
Property FSM.fsm_dot
:
A DOT syntax description of self.FSM_TRANSITIONS
.
Method 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 whenfsm_event
was callednew_state
: the new stateevent
: theevent
when
: a UNIX timestamp fromtime.time()
extra
: adict
with theextra
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.
Method FSM.fsm_event_is_allowed(self, event)
:
Test whether event
is permitted in the current state.
This can be handy as a pretest.
Property FSM.fsm_events
:
Return a list of the events valid for the current state.
Property FSM.fsm_history
:
History property wrapping private attribute.
This aids subclassing where the history is not a local attribute.
Method 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
.
Property FSM.fsm_svg
:
The state transition diagram as SVG.
Method FSM.fsm_transitions_as_dot(self, fsm_transitions=None, *, sep='\n', graph_name=None, history_style=None) -> str
:
Compute a DOT syntax graph description from a transitions dictionary.
Parameters:
* `fsm_transitions`: optional mapping of *state*->*event*->*state*,
default `self.FSM_TRANSITIONS`
* `sep`: optional separator between "lines", default `'
'*
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.
Usage::
T = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # Must be 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 definitions. See class Generic for more information on generic types. Generic functions work as follows:
def repeat(x: T, n: int) -> List[T]: '''Return a list containing n references to x.''' return [x]*n
def longest(x: A, y: A) -> A: '''Return the longest of two strings.''' return x if len(x) >= len(y) else y
The latter example's signature is essentially the overloading of (str, str) -> str and (bytes, bytes) -> bytes. Also note that if the arguments are instances of some subclass of str, the return type is still plain str.
At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError.
Type variables defined with covariant=True or contravariant=True can be used to declare covariant or contravariant generic types. See PEP 484 for more details. By default generic types are invariant in all type variables.
Type variables can be introspected. e.g.:
T.name == 'T' T.constraints == () T.covariant == False T.contravariant = False A.constraints == (str, bytes)
Note that only type variables defined in global scope can be pickled.
Class FSMTransitionEvent(builtins.tuple)
FSMTransitionEvent(old_state, new_state, event, when, extra)
Property FSMTransitionEvent.event
:
Alias for field number 2
Property FSMTransitionEvent.extra
:
Alias for field number 4
Property FSMTransitionEvent.new_state
:
Alias for field number 1
Property FSMTransitionEvent.old_state
:
Alias for field number 0
Property FSMTransitionEvent.when
:
Alias for field number 3
Release Log
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for cs.fsm-20240630-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3d9301658c142ad68938879573eefde27abe02361740051ba8db399878193ee0 |
|
MD5 | 2683d1b66fba1ad9e4cbfbeaadd2c526 |
|
BLAKE2b-256 | 1e9a08ce1c09c68faa708212cc911784c615768c81fa9270144956c60e2a3ff8 |