Skip to content

Commit

Permalink
Fixed linting and docstrings.
Browse files Browse the repository at this point in the history
  • Loading branch information
nazavode committed Sep 29, 2015
1 parent a2f9486 commit 166d1b6
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 2 deletions.
122 changes: 120 additions & 2 deletions automaton/automaton.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
A minimal Python finite-state machine implementation.
"""

from collections import namedtuple, defaultdict

Expand All @@ -26,10 +29,35 @@
"Automaton",
)


Event = namedtuple("Event", ["source_state", "dest_state"])
""" The class that represents an event. """


def connected_components(edges):
""" Finds the connected components in a graph.
Given a graph, this function finds its connected components using the
`Union Find`_ algorithm.
Parameters
----------
edges : dict
The list of graph edges.
The dictionary must have the following form::
{
('source_node_1', 'dest_node_1'),
# ...
('source_node_n', 'dest_node_n'),
}
Returns
-------
set
The set containing the nodes grouped by connected components.
.. _Union Find: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
"""
inbound = defaultdict(set)
nodes = set()
for source_node, dest_node in edges:
Expand All @@ -41,13 +69,13 @@ def connected_components(edges):
#
parent = {node: node for node in nodes}
#
def find(n): # pylint: disable=invalid-name
def find(n): # pylint: disable=invalid-name, missing-docstring
if parent[n] == n:
return n
else:
return find(parent[n])
#
def union(x, y): # pylint: disable=invalid-name
def union(x, y): # pylint: disable=invalid-name, missing-docstring
x_root = find(x)
y_root = find(y)
parent[x_root] = y_root
Expand All @@ -64,6 +92,12 @@ def union(x, y): # pylint: disable=invalid-name


class AutomatonMeta(type):
""" The metaclass that must be applied on the ``automaton``-enabled classes'
hierarchy.
The :meth:`~automaton.automaton.AutomatonMeta.__new__` method builds the
actual finite-state machine based on the user-provided events definition.
"""
def __new__(mcs, class_name, class_bases, class_dict):
cls = super().__new__(mcs, class_name, class_bases, class_dict)
events_to_states = {}
Expand Down Expand Up @@ -98,6 +132,24 @@ def __new__(mcs, class_name, class_bases, class_dict):


class Automaton(metaclass=AutomatonMeta):
""" Base class for automaton types.
Parameters
----------
initial_state : any, optional
The initial state for this automaton instance. Defaults to None.
Note that if automaton type has no default initial state
(specified via :attr:`~automaton.automaton.__default_initial_state__`),
this argument must be specified.
Raises
------
DefinitionError
The automaton type has no default initial state while no
custom initial state specified during construction *or* the
specified initial state is unknown.
"""

__default_initial_state__ = None

def __init__(self, initial_state=None):
Expand All @@ -118,33 +170,99 @@ def __init__(self, initial_state=None):

@property
def state(self):
""" Gets the current state of the automaton instance.
Returns
-------
any
The current state.
"""
return self._state

@state.setter
def state(self, next_state):
""" Sets the current state of the automaton instance.
Parameters
----------
next_state: any
The state to be set as automaton's state.
Returns
-------
any
The current state.
Raises
------
InvalidTransitionError
If the specified state is incompatible given the
automaton's transitions.
"""
transition = (self.state, next_state)
if transition not in self.__transitions__: # pylint: disable=no-member
raise InvalidTransitionError("No event {} found.".format(transition))
self._state = next_state

def event(self, do_event):
""" Signals the occurrence of an event and evolves the automaton
to the destination state.
Parameters
----------
do_event : any
The event to be performed on the automaton.
Raises
------
InvalidTransitionError
The specified event is unknown.
"""
if do_event not in self.events():
raise InvalidTransitionError("Unrecognized event '{}'.".format(do_event))
next_state = self.__events__[do_event][1] # pylint: disable=no-member
self.state = next_state

@classmethod
def states(cls):
""" Gives the automaton state set.
Returns
-------
iter
The iterator over the state set.
"""
yield from cls.__states__ # pylint: disable=no-member

@classmethod
def events(cls):
""" Gives the automaton events set.
Returns
-------
iter
The iterator over the events set.
"""
yield from cls.__events__ # pylint: disable=no-member

@classmethod
def transitions(cls):
""" Gives the automaton transition set.
Returns
-------
iter
The iterator over the transition set.
"""
yield from cls.__transitions__ # pylint: disable=no-member

@classmethod
def get_default_initial_state(cls):
""" Gives the automaton default initial state.
Returns
-------
any
The automaton default initial state.
"""
return cls.__default_initial_state__
6 changes: 6 additions & 0 deletions automaton/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Exceptions raised in the ``automaton`` package.
"""


__all__ = (
Expand All @@ -23,12 +26,15 @@


class AutomatonError(Exception):
""" Exception representing a generic error occurred in the automaton. """
pass


class DefinitionError(AutomatonError):
""" Exception representing an error occurred during the automaton definition. """
pass


class InvalidTransitionError(AutomatonError):
""" Exception representing an invalid transition. """
pass

0 comments on commit 166d1b6

Please sign in to comment.