[Python-modules-commits] [automat] 01/02: Import Upstream version 0.5.0

Free Ekanayaka freee at moszumanska.debian.org
Fri Feb 17 13:40:24 UTC 2017


This is an automated email from the git hooks/post-receive script.

freee pushed a commit to branch master
in repository automat.

commit c30152b9c0a070cb29ac68d8e9a3a8e8bd882b27
Author: Free Ekanayaka <freee at debian.org>
Date:   Fri Feb 17 11:41:57 2017 +0000

    Import Upstream version 0.5.0
---
 .gitignore                            |   6 +
 .travis.yml                           |  22 ++
 Automat.egg-info/PKG-INFO             | 448 +++++++++++++++++++++++++
 Automat.egg-info/SOURCES.txt          |  28 ++
 Automat.egg-info/dependency_links.txt |   1 +
 Automat.egg-info/entry_points.txt     |   3 +
 Automat.egg-info/requires.txt         |   6 +
 Automat.egg-info/top_level.txt        |   1 +
 LICENSE                               |  21 ++
 PKG-INFO                              | 448 +++++++++++++++++++++++++
 README.md                             | 421 +++++++++++++++++++++++
 automat/__init__.py                   |   8 +
 automat/_core.py                      | 149 +++++++++
 automat/_discover.py                  | 144 ++++++++
 automat/_introspection.py             |  41 +++
 automat/_methodical.py                | 309 +++++++++++++++++
 automat/_test/__init__.py             |   0
 automat/_test/test_core.py            |  86 +++++
 automat/_test/test_discover.py        | 609 ++++++++++++++++++++++++++++++++++
 automat/_test/test_methodical.py      | 400 ++++++++++++++++++++++
 automat/_test/test_visualize.py       | 430 ++++++++++++++++++++++++
 automat/_visualize.py                 | 182 ++++++++++
 docs/examples/automat_example.py      | 135 ++++++++
 docs/examples/io_coffee_example.py    |  39 +++
 docs/examples/lightswitch.py          |  39 +++
 docs/examples/turnstile_example.py    |  55 +++
 setup.cfg                             |   7 +
 setup.py                              |  47 +++
 tox.ini                               |  24 ++
 29 files changed, 4109 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f031edd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.tox/
+.coverage*
+*.egg-info/
+*.py[co]
+build/
+dist/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f5ea3c1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,22 @@
+language: python
+python: 2.7
+env:
+    - TOX_ENV=py27-extras
+    - TOX_ENV=py27-noextras
+    - TOX_ENV=pypy-extras
+    - TOX_ENV=pypy-noextras
+    - TOX_ENV=py33-extras
+    - TOX_ENV=py33-noextras
+    - TOX_ENV=py34-extras
+    - TOX_ENV=py34-noextras
+
+install:
+    - sudo apt-get install graphviz
+    - pip install tox coveralls
+
+script:
+    - tox -e $TOX_ENV
+
+after_success:
+    - tox -e coverage-report
+    - coveralls
diff --git a/Automat.egg-info/PKG-INFO b/Automat.egg-info/PKG-INFO
new file mode 100644
index 0000000..c173d43
--- /dev/null
+++ b/Automat.egg-info/PKG-INFO
@@ -0,0 +1,448 @@
+Metadata-Version: 1.0
+Name: Automat
+Version: 0.5.0
+Summary: Self-service finite-state machines for the programmer on the go.
+Home-page: https://github.com/glyph/Automat
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: MIT
+Description: 
+        Automat
+        =======
+        
+        
+        .. image:: https://travis-ci.org/glyph/automat.svg?branch=master
+           :target: https://travis-ci.org/glyph/automat
+           :alt: Build Status
+        
+        
+        .. image:: https://coveralls.io/repos/glyph/automat/badge.png
+           :target: https://coveralls.io/r/glyph/automat
+           :alt: Coverage Status
+        
+        
+        Self-service finite-state machines for the programmer on the go.
+        ----------------------------------------------------------------
+        
+        Automat is a library for concise, idiomatic Python expression of finite-state
+        automata (particularly deterministic finite-state transducers).
+        
+        Why use state machines?
+        ^^^^^^^^^^^^^^^^^^^^^^^
+        
+        Sometimes you have to create an object whose behavior varies with its state,
+        but still wishes to present a consistent interface to its callers.
+        
+        For example, let's say you're writing the software for a coffee machine.  It
+        has a lid that can be opened or closed, a chamber for water, a chamber for
+        coffee beans, and a button for "brew".
+        
+        There are a number of possible states for the coffee machine.  It might or
+        might not have water.  It might or might not have beans.  The lid might be open
+        or closed.  The "brew" button should only actually attempt to brew coffee in
+        one of these configurations, and the "open lid" button should only work if the
+        coffee is not, in fact, brewing.
+        
+        With diligence and attention to detail, you can implement this correctly using
+        a collection of attributes on an object; ``has_water``\ , ``has_beans``\ ,
+        ``is_lid_open`` and so on.  However, you have to keep all these attributes
+        consistent.  As the coffee maker becomes more complex - perhaps you add an
+        additional chamber for flavorings so you can make hazelnut coffee, for
+        example - you have to keep adding more and more checks and more and more
+        reasoning about which combinations of states are allowed.
+        
+        Rather than adding tedious 'if' checks to every single method to make sure that
+        each of these flags are exactly what you expect, you can use a state machine to
+        ensure that if your code runs at all, it will be run with all the required
+        values initialized, because they have to be called in the order you declare
+        them.
+        
+        You can read about state machines and their advantages for Python programmers
+        in considerably more detail
+        `in this excellent series of articles from ClusterHQ <https://clusterhq.com/blog/what-is-a-state-machine/>`_.
+        
+        What makes Automat different?
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        There are
+        `dozens of libraries on PyPI implementing state machines <https://pypi.org/search/?q=finite+state+machine>`_.
+        So it behooves me to say why yet another one would be a good idea.
+        
+        Automat is designed around this principle: while organizing your code around
+        state machines is a good idea, your callers don't, and shouldn't have to, care
+        that you've done so.  In Python, the "input" to a stateful system is a method
+        call; the "output" may be a method call, if you need to invoke a side effect,
+        or a return value, if you are just performing a computation in memory.  Most
+        other state-machine libraries require you to explicitly create an input object,
+        provide that object to a generic "input" method, and then receive results,
+        sometimes in terms of that library's interfaces and sometimes in terms of
+        classes you define yourself.
+        
+        For example, a snippet of the coffee-machine example above might be implemented
+        as follows in naive Python:
+        
+        .. code-block:: python
+        
+           class CoffeeMachine(object):
+               def brew_button(self):
+                   if self.has_water and self.has_beans and not self.is_lid_open:
+                       self.heat_the_heating_element()
+                       # ...
+        
+        With Automat, you'd create a class with a ``MethodicalMachine`` attribute:
+        
+        .. code-block:: python
+        
+           from automat import MethodicalMachine
+        
+           class CoffeeBrewer(object):
+               _machine = MethodicalMachine()
+        
+        and then you would break the above logic into two pieces - the ``brew_button``
+        *input*\ , declared like so:
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def brew_button(self):
+                   "The user pressed the 'brew' button."
+        
+        It wouldn't do any good to declare a method *body* on this, however, because
+        input methods don't actually execute their bodies when called; doing actual
+        work is the *output*\ 's job:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _heat_the_heating_element(self):
+                   "Heat up the heating element, which should cause coffee to happen."
+                   self._heating_element.turn_on()
+        
+        As well as a couple of *states* - and for simplicity's sake let's say that the
+        only two states are ``have_beans`` and ``dont_have_beans``\ :
+        
+        .. code-block:: python
+        
+               @_machine.state()
+               def have_beans(self):
+                   "In this state, you have some beans."
+               @_machine.state(initial=True)
+               def dont_have_beans(self):
+                   "In this state, you don't have any beans."
+        
+        ``have_beans`` is the ``initial`` state because ``CoffeeBrewer`` starts without beans
+        in it.
+        
+        (And another input to put some beans in:)
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def put_in_beans(self):
+                   "The user put in some beans."
+        
+        Finally, you hook everything together with the ``upon`` method of the functions
+        decorated with ``machine.state``\ :
+        
+        .. code-block:: python
+        
+        
+               # When we don't have beans, upon putting in beans, we will then have beans
+               # (and produce no output)
+               dont_have_beans.upon(put_in_beans, enter=have_beans, outputs=[])
+        
+               # When we have beans, upon pressing the brew button, we will then not have
+               # beans any more (as they have been entered into the brewing chamber) and
+               # our output will be heating the heating element.
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element])
+        
+        To *users* of this coffee machine class though, it still looks like a POPO
+        (Plain Old Python Object):
+        
+        .. code-block:: python
+        
+           >>> coffee_machine = CoffeeMachine()
+           >>> coffee_machine.put_in_beans()
+           >>> coffee_machine.brew_button()
+        
+        All of the *inputs* are provided by calling them like methods, all of the
+        *outputs* are automatically invoked when they are produced according to the
+        outputs specified to ``upon`` and all of the states are simply opaque tokens -
+        although the fact that they're defined as methods like inputs and outputs
+        allows you to put docstrings on them easily to document them.
+        
+        How do I get the current state of a state machine?
+        --------------------------------------------------
+        
+        Don't do that.
+        
+        One major reason for having a state machine is that you want the callers of the
+        state machine to just provide the appropriate input to the machine at the
+        appropriate time, and *not have to check themselves* what state the machine is
+        in.  So if you are tempted to write some code like this:
+        
+        .. code-block:: python
+        
+           if connection_state_machine.state == "CONNECTED":
+               connection_state_machine.send_message()
+           else:
+               print("not connected")
+        
+        Instead, just make your calling code do this:
+        
+        .. code-block:: python
+        
+           connection_state_machine.send_message()
+        
+        and then change your state machine to look like this:
+        
+        .. code-block:: python
+        
+               @machine.state()
+               def connected(self):
+                   "connected"
+               @machine.state()
+               def not_connected(self):
+                   "not connected"
+               @machine.input()
+               def send_message(self):
+                   "send a message"
+               @machine.output()
+               def _actually_send_message(self):
+                   self._transport.send(b"message")
+               @machine.output()
+               def _report_sending_failure(self):
+                   print("not connected")
+               connected.upon(send_message, enter=connected, [_actually_send_message])
+               not_connected.upon(send_message, enter=not_connected, [_report_sending_failure])
+        
+        so that the responsibility for knowing which state the state machine is in
+        remains within the state machine itself.
+        
+        Input for Inputs and Output for Outputs
+        ---------------------------------------
+        
+        Quite often you want to be able to pass parameters to your methods, as well as
+        inspecting their results.  For example, when you brew the coffee, you might
+        expect a cup of coffee to result, and you would like to see what kind of coffee
+        it is.  And if you were to put delicious hand-roasted small-batch artisanal
+        beans into the machine, you would expect a *better* cup of coffee than if you
+        were to use mass-produced beans.  You would do this in plain old Python by
+        adding a parameter, so that's how you do it in Automat as well.
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def put_in_beans(self, beans):
+                   "The user put in some beans."
+        
+        However, one important difference here is that *we can't add any
+        implementation code to the input method*.  Inputs are purely a declaration of
+        the interface; the behavior must all come from outputs.  Therefore, the change
+        in the state of the coffee machine must be represented as an output.  We can
+        add an output method like this:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _save_beans(self, beans):
+                   "The beans are now in the machine; save them."
+                   self._beans = beans
+        
+        and then connect it to the ``put_in_beans`` by changing the transition from
+        ``dont_have_beans`` to ``have_beans`` like so:
+        
+        .. code-block:: python
+        
+               dont_have_beans.upon(put_in_beans, enter=have_beans,
+                                    outputs=[_save_beans])
+        
+        Now, when you call:
+        
+        .. code-block:: python
+        
+           coffee_machine.put_in_beans("real good beans")
+        
+        the machine will remember the beans for later.
+        
+        So how do we get the beans back out again?  One of our outputs needs to have a
+        return value.  It would make sense if our ``brew_button`` method returned the cup
+        of coffee that it made, so we should add an output.  So, in addition to heating
+        the heating element, let's add a return value that describes the coffee.  First
+        a new output:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _describe_coffee(self):
+                   return "A cup of coffee made with {}.".format(self._beans)
+        
+        Note that we don't need to check first whether ``self._beans`` exists or not,
+        because we can only reach this output method if the state machine says we've
+        gone through a set of states that sets this attribute.
+        
+        Now, we need to hook up ``_describe_coffee`` to the process of brewing, so change
+        the brewing transition to:
+        
+        .. code-block:: python
+        
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element,
+                                        _describe_coffee])
+        
+        Now, we can call it:
+        
+        .. code-block:: python
+        
+           >>> coffee_machine.brew_button()
+           [None, 'A cup of coffee made with real good beans.']
+        
+        Except... wait a second, what's that ``None`` doing there?
+        
+        Since every input can produce multiple outputs, in automat, the default return
+        value from every input invocation is a ``list``.  In this case, we have both
+        ``_heat_the_heating_element`` and ``_describe_coffee`` outputs, so we're seeing
+        both of their return values.  However, this can be customized, with the
+        ``collector`` argument to ``upon``\ ; the ``collector`` is a callable which takes an
+        iterable of all the outputs' return values and "collects" a single return value
+        to return to the caller of the state machine.
+        
+        In this case, we only care about the last output, so we can adjust the call to
+        ``upon`` like this:
+        
+        .. code-block:: python
+        
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element,
+                                        _describe_coffee],
+                               collector=lambda iterable: list(iterable)[-1]
+               )
+        
+        And now, we'll get just the return value we want:
+        
+        .. code-block:: python
+        
+           >>> coffee_machine.brew_button()
+           'A cup of coffee made with real good beans.'
+        
+        If I can't get the state of the state machine, how can I save it to (a database, an API response, a file on disk...)
+        --------------------------------------------------------------------------------------------------------------------
+        
+        There are APIs for serializing the state machine.
+        
+        First, you have to decide on a persistent representation of each state, via the
+        ``serialized=`` argument to the ``MethodicalMachine.state()`` decorator.
+        
+        Let's take this very simple "light switch" state machine, which can be on or
+        off, and flipped to reverse its state:
+        
+        .. code-block:: python
+        
+           class LightSwitch(object):
+               machine = MethodicalMachine()
+               @machine.state(serialized="on")
+               def on_state(self):
+                   "the switch is on"
+               @machine.state(serialized="off", initial=True)
+               def off_state(self):
+                   "the switch is off"
+               @machine.input()
+               def flip(self):
+                   "flip the switch"
+               on_state.upon(flip, enter=off_state, outputs=[])
+               off_state.upon(flip, enter=on_state, outputs=[])
+        
+        In this case, we've chosen a serialized representation for each state via the
+        ``serialized`` argument.  The on state is represented by the string ``"on"``\ , and
+        the off state is represented by the string ``"off"``.
+        
+        Now, let's just add an input that lets us tell if the switch is on or not.
+        
+        .. code-block:: python
+        
+               @machine.input()
+               def query_power(self):
+                   "return True if powered, False otherwise"
+               @machine.output()
+               def _is_powered(self):
+                   return True
+               @machine.output()
+               def _not_powered(self):
+                   return False
+               on_state.upon(query_power, enter=on_state, outputs=[_is_powered],
+                             collector=next)
+               off_state.upon(query_power, enter=off_state, outputs=[_not_powered],
+                              collector=next)
+        
+        To save the state, we have the ``MethodicalMachine.serializer()`` method.  A
+        method decorated with ``@serializer()`` gets an extra argument injected at the
+        beginning of its argument list: the serialized identifier for the state.  In
+        this case, either ``"on"`` or ``"off"``.  Since state machine output methods can
+        also affect other state on the object, a serializer method is expected to
+        return *all* relevant state for serialization.
+        
+        For our simple light switch, such a method might look like this:
+        
+        .. code-block:: python
+        
+               @machine.serializer()
+               def save(self, state):
+                   return {"is-it-on": state}
+        
+        Serializers can be public methods, and they can return whatever you like.  If
+        necessary, you can have different serializers - just multiple methods decorated
+        with ``@machine.serializer()`` - for different formats; return one data-structure
+        for JSON, one for XML, one for a database row, and so on.
+        
+        When it comes time to unserialize, though, you generally want a private method,
+        because an unserializer has to take a not-fully-initialized instance and
+        populate it with state.  It is expected to *return* the serialized machine
+        state token that was passed to the serializer, but it can take whatever
+        arguments you like.  Of course, in order to return that, it probably has to
+        take it somewhere in its arguments, so it will generally take whatever a paired
+        serializer has returned as an argument.
+        
+        So our unserializer would look like this:
+        
+        .. code-block:: python
+        
+               @machine.unserializer()
+               def _restore(self, blob):
+                   return blob["is-it-on"]
+        
+        Generally you will want a classmethod deserialization constructor which you
+        write yourself to call this, so that you know how to create an instance of your
+        own object, like so:
+        
+        .. code-block:: python
+        
+               @classmethod
+               def from_blob(cls, blob):
+                   self = cls()
+                   self._restore(blob)
+                   return self
+        
+        Saving and loading our ``LightSwitch`` along with its state-machine state can now
+        be accomplished as follows:
+        
+        .. code-block:: python
+        
+           >>> switch1 = LightSwitch()
+           >>> switch1.query_power()
+           False
+           >>> switch1.flip()
+           []
+           >>> switch1.query_power()
+           True
+           >>> blob = switch1.save()
+           >>> switch2 = LightSwitch.from_blob(blob)
+           >>> switch2.query_power()
+           True
+        
+        More comprehensive (tested, working) examples are present in ``docs/examples``.
+        
+        Go forth and machine all the state!
+        
+Keywords: fsm finite state machine automata
+Platform: UNKNOWN
diff --git a/Automat.egg-info/SOURCES.txt b/Automat.egg-info/SOURCES.txt
new file mode 100644
index 0000000..6511a7b
--- /dev/null
+++ b/Automat.egg-info/SOURCES.txt
@@ -0,0 +1,28 @@
+.gitignore
+.travis.yml
+LICENSE
+README.md
+setup.cfg
+setup.py
+tox.ini
+Automat.egg-info/PKG-INFO
+Automat.egg-info/SOURCES.txt
+Automat.egg-info/dependency_links.txt
+Automat.egg-info/entry_points.txt
+Automat.egg-info/requires.txt
+Automat.egg-info/top_level.txt
+automat/__init__.py
+automat/_core.py
+automat/_discover.py
+automat/_introspection.py
+automat/_methodical.py
+automat/_visualize.py
+automat/_test/__init__.py
+automat/_test/test_core.py
+automat/_test/test_discover.py
+automat/_test/test_methodical.py
+automat/_test/test_visualize.py
+docs/examples/automat_example.py
+docs/examples/io_coffee_example.py
+docs/examples/lightswitch.py
+docs/examples/turnstile_example.py
\ No newline at end of file
diff --git a/Automat.egg-info/dependency_links.txt b/Automat.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Automat.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/Automat.egg-info/entry_points.txt b/Automat.egg-info/entry_points.txt
new file mode 100644
index 0000000..d793199
--- /dev/null
+++ b/Automat.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+automat-visualize = automat._visualize:tool
+
diff --git a/Automat.egg-info/requires.txt b/Automat.egg-info/requires.txt
new file mode 100644
index 0000000..d1c9e61
--- /dev/null
+++ b/Automat.egg-info/requires.txt
@@ -0,0 +1,6 @@
+attrs
+six
+
+[visualize]
+graphviz>0.5.1
+Twisted>=16.1.1
diff --git a/Automat.egg-info/top_level.txt b/Automat.egg-info/top_level.txt
new file mode 100644
index 0000000..b69387b
--- /dev/null
+++ b/Automat.egg-info/top_level.txt
@@ -0,0 +1 @@
+automat
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9773501
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2014
+Rackspace
+
+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.
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..c173d43
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,448 @@
+Metadata-Version: 1.0
+Name: Automat
+Version: 0.5.0
+Summary: Self-service finite-state machines for the programmer on the go.
+Home-page: https://github.com/glyph/Automat
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: MIT
+Description: 
+        Automat
+        =======
+        
+        
+        .. image:: https://travis-ci.org/glyph/automat.svg?branch=master
+           :target: https://travis-ci.org/glyph/automat
+           :alt: Build Status
+        
+        
+        .. image:: https://coveralls.io/repos/glyph/automat/badge.png
+           :target: https://coveralls.io/r/glyph/automat
+           :alt: Coverage Status
+        
+        
+        Self-service finite-state machines for the programmer on the go.
+        ----------------------------------------------------------------
+        
+        Automat is a library for concise, idiomatic Python expression of finite-state
+        automata (particularly deterministic finite-state transducers).
+        
+        Why use state machines?
+        ^^^^^^^^^^^^^^^^^^^^^^^
+        
+        Sometimes you have to create an object whose behavior varies with its state,
+        but still wishes to present a consistent interface to its callers.
+        
+        For example, let's say you're writing the software for a coffee machine.  It
+        has a lid that can be opened or closed, a chamber for water, a chamber for
+        coffee beans, and a button for "brew".
+        
+        There are a number of possible states for the coffee machine.  It might or
+        might not have water.  It might or might not have beans.  The lid might be open
+        or closed.  The "brew" button should only actually attempt to brew coffee in
+        one of these configurations, and the "open lid" button should only work if the
+        coffee is not, in fact, brewing.
+        
+        With diligence and attention to detail, you can implement this correctly using
+        a collection of attributes on an object; ``has_water``\ , ``has_beans``\ ,
+        ``is_lid_open`` and so on.  However, you have to keep all these attributes
+        consistent.  As the coffee maker becomes more complex - perhaps you add an
+        additional chamber for flavorings so you can make hazelnut coffee, for
+        example - you have to keep adding more and more checks and more and more
+        reasoning about which combinations of states are allowed.
+        
+        Rather than adding tedious 'if' checks to every single method to make sure that
+        each of these flags are exactly what you expect, you can use a state machine to
+        ensure that if your code runs at all, it will be run with all the required
+        values initialized, because they have to be called in the order you declare
+        them.
+        
+        You can read about state machines and their advantages for Python programmers
+        in considerably more detail
+        `in this excellent series of articles from ClusterHQ <https://clusterhq.com/blog/what-is-a-state-machine/>`_.
+        
+        What makes Automat different?
+        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+        
+        There are
+        `dozens of libraries on PyPI implementing state machines <https://pypi.org/search/?q=finite+state+machine>`_.
+        So it behooves me to say why yet another one would be a good idea.
+        
+        Automat is designed around this principle: while organizing your code around
+        state machines is a good idea, your callers don't, and shouldn't have to, care
+        that you've done so.  In Python, the "input" to a stateful system is a method
+        call; the "output" may be a method call, if you need to invoke a side effect,
+        or a return value, if you are just performing a computation in memory.  Most
+        other state-machine libraries require you to explicitly create an input object,
+        provide that object to a generic "input" method, and then receive results,
+        sometimes in terms of that library's interfaces and sometimes in terms of
+        classes you define yourself.
+        
+        For example, a snippet of the coffee-machine example above might be implemented
+        as follows in naive Python:
+        
+        .. code-block:: python
+        
+           class CoffeeMachine(object):
+               def brew_button(self):
+                   if self.has_water and self.has_beans and not self.is_lid_open:
+                       self.heat_the_heating_element()
+                       # ...
+        
+        With Automat, you'd create a class with a ``MethodicalMachine`` attribute:
+        
+        .. code-block:: python
+        
+           from automat import MethodicalMachine
+        
+           class CoffeeBrewer(object):
+               _machine = MethodicalMachine()
+        
+        and then you would break the above logic into two pieces - the ``brew_button``
+        *input*\ , declared like so:
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def brew_button(self):
+                   "The user pressed the 'brew' button."
+        
+        It wouldn't do any good to declare a method *body* on this, however, because
+        input methods don't actually execute their bodies when called; doing actual
+        work is the *output*\ 's job:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _heat_the_heating_element(self):
+                   "Heat up the heating element, which should cause coffee to happen."
+                   self._heating_element.turn_on()
+        
+        As well as a couple of *states* - and for simplicity's sake let's say that the
+        only two states are ``have_beans`` and ``dont_have_beans``\ :
+        
+        .. code-block:: python
+        
+               @_machine.state()
+               def have_beans(self):
+                   "In this state, you have some beans."
+               @_machine.state(initial=True)
+               def dont_have_beans(self):
+                   "In this state, you don't have any beans."
+        
+        ``have_beans`` is the ``initial`` state because ``CoffeeBrewer`` starts without beans
+        in it.
+        
+        (And another input to put some beans in:)
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def put_in_beans(self):
+                   "The user put in some beans."
+        
+        Finally, you hook everything together with the ``upon`` method of the functions
+        decorated with ``machine.state``\ :
+        
+        .. code-block:: python
+        
+        
+               # When we don't have beans, upon putting in beans, we will then have beans
+               # (and produce no output)
+               dont_have_beans.upon(put_in_beans, enter=have_beans, outputs=[])
+        
+               # When we have beans, upon pressing the brew button, we will then not have
+               # beans any more (as they have been entered into the brewing chamber) and
+               # our output will be heating the heating element.
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element])
+        
+        To *users* of this coffee machine class though, it still looks like a POPO
+        (Plain Old Python Object):
+        
+        .. code-block:: python
+        
+           >>> coffee_machine = CoffeeMachine()
+           >>> coffee_machine.put_in_beans()
+           >>> coffee_machine.brew_button()
+        
+        All of the *inputs* are provided by calling them like methods, all of the
+        *outputs* are automatically invoked when they are produced according to the
+        outputs specified to ``upon`` and all of the states are simply opaque tokens -
+        although the fact that they're defined as methods like inputs and outputs
+        allows you to put docstrings on them easily to document them.
+        
+        How do I get the current state of a state machine?
+        --------------------------------------------------
+        
+        Don't do that.
+        
+        One major reason for having a state machine is that you want the callers of the
+        state machine to just provide the appropriate input to the machine at the
+        appropriate time, and *not have to check themselves* what state the machine is
+        in.  So if you are tempted to write some code like this:
+        
+        .. code-block:: python
+        
+           if connection_state_machine.state == "CONNECTED":
+               connection_state_machine.send_message()
+           else:
+               print("not connected")
+        
+        Instead, just make your calling code do this:
+        
+        .. code-block:: python
+        
+           connection_state_machine.send_message()
+        
+        and then change your state machine to look like this:
+        
+        .. code-block:: python
+        
+               @machine.state()
+               def connected(self):
+                   "connected"
+               @machine.state()
+               def not_connected(self):
+                   "not connected"
+               @machine.input()
+               def send_message(self):
+                   "send a message"
+               @machine.output()
+               def _actually_send_message(self):
+                   self._transport.send(b"message")
+               @machine.output()
+               def _report_sending_failure(self):
+                   print("not connected")
+               connected.upon(send_message, enter=connected, [_actually_send_message])
+               not_connected.upon(send_message, enter=not_connected, [_report_sending_failure])
+        
+        so that the responsibility for knowing which state the state machine is in
+        remains within the state machine itself.
+        
+        Input for Inputs and Output for Outputs
+        ---------------------------------------
+        
+        Quite often you want to be able to pass parameters to your methods, as well as
+        inspecting their results.  For example, when you brew the coffee, you might
+        expect a cup of coffee to result, and you would like to see what kind of coffee
+        it is.  And if you were to put delicious hand-roasted small-batch artisanal
+        beans into the machine, you would expect a *better* cup of coffee than if you
+        were to use mass-produced beans.  You would do this in plain old Python by
+        adding a parameter, so that's how you do it in Automat as well.
+        
+        .. code-block:: python
+        
+               @_machine.input()
+               def put_in_beans(self, beans):
+                   "The user put in some beans."
+        
+        However, one important difference here is that *we can't add any
+        implementation code to the input method*.  Inputs are purely a declaration of
+        the interface; the behavior must all come from outputs.  Therefore, the change
+        in the state of the coffee machine must be represented as an output.  We can
+        add an output method like this:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _save_beans(self, beans):
+                   "The beans are now in the machine; save them."
+                   self._beans = beans
+        
+        and then connect it to the ``put_in_beans`` by changing the transition from
+        ``dont_have_beans`` to ``have_beans`` like so:
+        
+        .. code-block:: python
+        
+               dont_have_beans.upon(put_in_beans, enter=have_beans,
+                                    outputs=[_save_beans])
+        
+        Now, when you call:
+        
+        .. code-block:: python
+        
+           coffee_machine.put_in_beans("real good beans")
+        
+        the machine will remember the beans for later.
+        
+        So how do we get the beans back out again?  One of our outputs needs to have a
+        return value.  It would make sense if our ``brew_button`` method returned the cup
+        of coffee that it made, so we should add an output.  So, in addition to heating
+        the heating element, let's add a return value that describes the coffee.  First
+        a new output:
+        
+        .. code-block:: python
+        
+               @_machine.output()
+               def _describe_coffee(self):
+                   return "A cup of coffee made with {}.".format(self._beans)
+        
+        Note that we don't need to check first whether ``self._beans`` exists or not,
+        because we can only reach this output method if the state machine says we've
+        gone through a set of states that sets this attribute.
+        
+        Now, we need to hook up ``_describe_coffee`` to the process of brewing, so change
+        the brewing transition to:
+        
+        .. code-block:: python
+        
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element,
+                                        _describe_coffee])
+        
+        Now, we can call it:
+        
+        .. code-block:: python
+        
+           >>> coffee_machine.brew_button()
+           [None, 'A cup of coffee made with real good beans.']
+        
+        Except... wait a second, what's that ``None`` doing there?
+        
+        Since every input can produce multiple outputs, in automat, the default return
+        value from every input invocation is a ``list``.  In this case, we have both
+        ``_heat_the_heating_element`` and ``_describe_coffee`` outputs, so we're seeing
+        both of their return values.  However, this can be customized, with the
+        ``collector`` argument to ``upon``\ ; the ``collector`` is a callable which takes an
+        iterable of all the outputs' return values and "collects" a single return value
+        to return to the caller of the state machine.
+        
+        In this case, we only care about the last output, so we can adjust the call to
+        ``upon`` like this:
+        
+        .. code-block:: python
+        
+               have_beans.upon(brew_button, enter=dont_have_beans,
+                               outputs=[_heat_the_heating_element,
+                                        _describe_coffee],
+                               collector=lambda iterable: list(iterable)[-1]
+               )
+        
+        And now, we'll get just the return value we want:
+        
+        .. code-block:: python
+        
+           >>> coffee_machine.brew_button()
+           'A cup of coffee made with real good beans.'
+        
+        If I can't get the state of the state machine, how can I save it to (a database, an API response, a file on disk...)
+        --------------------------------------------------------------------------------------------------------------------
+        
+        There are APIs for serializing the state machine.
+        
+        First, you have to decide on a persistent representation of each state, via the
+        ``serialized=`` argument to the ``MethodicalMachine.state()`` decorator.
+        
+        Let's take this very simple "light switch" state machine, which can be on or
+        off, and flipped to reverse its state:
+        
+        .. code-block:: python
+        
+           class LightSwitch(object):
+               machine = MethodicalMachine()
+               @machine.state(serialized="on")
+               def on_state(self):
+                   "the switch is on"
+               @machine.state(serialized="off", initial=True)
+               def off_state(self):
+                   "the switch is off"
+               @machine.input()
+               def flip(self):
+                   "flip the switch"
+               on_state.upon(flip, enter=off_state, outputs=[])
+               off_state.upon(flip, enter=on_state, outputs=[])
+        
+        In this case, we've chosen a serialized representation for each state via the
+        ``serialized`` argument.  The on state is represented by the string ``"on"``\ , and
+        the off state is represented by the string ``"off"``.
+        
+        Now, let's just add an input that lets us tell if the switch is on or not.
+        
+        .. code-block:: python
+        
+               @machine.input()
+               def query_power(self):
+                   "return True if powered, False otherwise"
... 3318 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/automat.git



More information about the Python-modules-commits mailing list