Metadata-Version: 2.4
Name: cs-fsm
Version: 20261016
Summary: Basic Finite State Machine (FSM) tools.
Keywords: python3
Author-email: Cameron Simpson <cs@cskk.id.au>
Description-Content-Type: text/markdown
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Requires-Dist: cs.gimmicks>=20260311
Requires-Dist: cs.gvutils>=20230816
Requires-Dist: cs.lex>=20261016
Requires-Dist: cs.pfx>=20260912
Requires-Dist: cs.seq>=20260912
Requires-Dist: typeguard
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/fsm.py

Basic Finite State Machine (FSM) tools.



Short summary:


* `CancellationError`: Subclass of `FSMError` Raised when trying to make use of an `FSM` which is cancelled.


* `FSM`: Base class for a finite state machine (FSM).


* `FSMError`: An exception associated with an `FSM`.


* `FSMSubType`: Type variable.


* `FSMTransitionEvent`: FSMTransitionEvent(old_state, new_state, event, when, extra).

# Symbols

```

```
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)]:
        ...

Type variables can also have defaults:

    class IntDefault[T = int]:
        ...

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
   D = TypeVar('D', default=int)  # Defaults to int

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.
```


FSMSubType = ~FSMSubType

```

# Classes

## 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`.

<figure>
    <svg width="8pt" height="8pt"
   viewBox="0.00 0.00 8.00 8.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 4)">
  <title>FSM State Diagram</title>
  <polygon fill="white" stroke="none" points="-4,4 -4,-4 4,-4 4,4 -4,4"/>
  </g>
  </svg>
  <figcaption>FSM State Diagram</figcaption>
</figure>


### `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.FSM_ANY_STATE`

    <object object at 0x10c4ed270>

### `FSM.FSM_TRANSITIONS`

    {}

### `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`

    <property object at 0x10e10eca0>

### `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`

    <property object at 0x10e10ec50>

### `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`

    <property object at 0x10e10ebb0>

### `FSM.fsm_history`

    <property object at 0x10e10f010>

### `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 the transitions
* `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`

    <property object at 0x10e10ed40>

### `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.

## class FSMTransitionEvent(builtins.tuple)

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

### `FSMTransitionEvent.__replace__(self, /, **kwds)`

Return a new FSMTransitionEvent object replacing specified fields with new values

### `FSMTransitionEvent.event`

    _tuplegetter(2, 'Alias for field number 2')

### `FSMTransitionEvent.extra`

    _tuplegetter(4, 'Alias for field number 4')

### `FSMTransitionEvent.new_state`

    _tuplegetter(1, 'Alias for field number 1')

### `FSMTransitionEvent.old_state`

    _tuplegetter(0, 'Alias for field number 0')

### `FSMTransitionEvent.when`

    _tuplegetter(3, 'Alias for field number 3')
