Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-Automat for openSUSE:Factory 
checked in at 2023-01-11 17:14:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Automat (Old)
 and      /work/SRC/openSUSE:Factory/.python-Automat.new.32243 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Automat"

Wed Jan 11 17:14:15 2023 rev:6 rq:1057654 version:22.10.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Automat/python-Automat.changes    
2021-12-12 21:27:41.084344216 +0100
+++ /work/SRC/openSUSE:Factory/.python-Automat.new.32243/python-Automat.changes 
2023-01-11 17:14:16.943557337 +0100
@@ -1,0 +2,8 @@
+Tue Jan 10 11:21:35 UTC 2023 - Daniel Garcia <daniel.gar...@suse.com>
+
+- Update to 22.10.0
+  * Fix _test_visualize.py twisted import errors
+  * Fix #17: Allow enter to have a default
+  * Use CodeType.replace() in copycode for Python > 3.8
+
+-------------------------------------------------------------------

Old:
----
  Automat-20.2.0.tar.gz

New:
----
  Automat-22.10.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Automat.spec ++++++
--- /var/tmp/diff_new_pack.CC2mah/_old  2023-01-11 17:14:17.623561293 +0100
+++ /var/tmp/diff_new_pack.CC2mah/_new  2023-01-11 17:14:17.627561316 +0100
@@ -1,7 +1,7 @@
 #
 # spec file
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -25,10 +25,9 @@
 %bcond_with test
 %endif
 
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %bcond_without python2
 Name:           python-Automat%{psuffix}
-Version:        20.2.0
+Version:        22.10.0
 Release:        0
 Summary:        Self-service finite-state machines for the programmer on the go
 License:        MIT
@@ -36,10 +35,10 @@
 Source:         
https://files.pythonhosted.org/packages/source/A/Automat/Automat-%{version}.tar.gz
 BuildRequires:  %{python_module setuptools_scm}
 BuildRequires:  %{python_module setuptools}
+BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-attrs >= 16.1.0
-Requires:       python-six
+Requires:       python-attrs >= 19.2.0
 Requires(post): update-alternatives
 Requires(preun):update-alternatives
 Suggests:       python-Twisted >= 16.1.1
@@ -47,7 +46,7 @@
 BuildArch:      noarch
 %if %{with test}
 BuildRequires:  %{python_module Twisted >= 16.1.1}
-BuildRequires:  %{python_module attrs >= 16.1.0}
+BuildRequires:  %{python_module attrs >= 19.2.0}
 BuildRequires:  %{python_module graphviz >= 0.5.1}
 BuildRequires:  %{python_module pytest}
 %if %{with python2}

++++++ Automat-20.2.0.tar.gz -> Automat-22.10.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/.github/workflows/ci.yml 
new/Automat-22.10.0/.github/workflows/ci.yml
--- old/Automat-20.2.0/.github/workflows/ci.yml 1970-01-01 01:00:00.000000000 
+0100
+++ new/Automat-22.10.0/.github/workflows/ci.yml        2022-10-29 
08:56:12.000000000 +0200
@@ -0,0 +1,41 @@
+name: ci
+
+on:
+  push:
+    branches:
+      - trunk
+
+  pull_request:
+    branches:
+      - trunk
+
+jobs:
+  build:
+    name: ${{ matrix.TOX_ENV }}
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python: ["3.10"]
+        TOX_ENV: ["py310-extras", "py310-noextras", "mypy"]
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python
+        uses: actions/setup-python@v3
+        with:
+          python-version: ${{ matrix.python }}
+      - name: Tox Run
+        run:  |
+          pip install tox;
+          TOX_ENV="${{ matrix.TOX_ENV }}";
+          echo "Starting: ${TOX_ENV} ${PUSH_DOCS}"
+          if [[ -n "${TOX_ENV}" ]]; then
+            tox -e "$TOX_ENV";
+            if [[ "${TOX_ENV}" != "mypy" ]]; then
+              tox -e coverage-report;
+            fi;
+          fi;
+      - name: Upload coverage report
+        if: ${{ matrix.TOX_ENV != 'mypy' }}
+        uses: codecov/codecov-action@v3.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/.travis.yml 
new/Automat-22.10.0/.travis.yml
--- old/Automat-20.2.0/.travis.yml      2020-02-16 20:33:10.000000000 +0100
+++ new/Automat-22.10.0/.travis.yml     2022-10-29 08:56:09.000000000 +0200
@@ -6,15 +6,10 @@
 
 matrix:
   include:
-    - python: 2.7
-      env: TOX_ENV=py27-extras
-    - python: 2.7
-      env: TOX_ENV=py27-noextras
-
-    - python: pypy
-      env: TOX_ENV=pypy-extras
-    - python: pypy
-      env: TOX_ENV=pypy-noextras
+    - python: pypy3
+      env: TOX_ENV=pypy3-extras
+    - python: pypy3
+      env: TOX_ENV=pypy3-noextras
 
     - python: 3.5
       env: TOX_ENV=py35-extras
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/Automat.egg-info/PKG-INFO 
new/Automat-22.10.0/Automat.egg-info/PKG-INFO
--- old/Automat-20.2.0/Automat.egg-info/PKG-INFO        2020-02-16 
20:36:16.000000000 +0100
+++ new/Automat-22.10.0/Automat.egg-info/PKG-INFO       2022-10-29 
09:03:31.000000000 +0200
@@ -1,472 +1,12 @@
 Metadata-Version: 2.1
 Name: Automat
-Version: 20.2.0
+Version: 22.10.0
 Summary: Self-service finite-state machines for the programmer on the go.
 Home-page: https://github.com/glyph/Automat
 Author: Glyph
 Author-email: gl...@twistedmatrix.com
 License: MIT
-Description: 
-        Automat
-        =======
-        
-        
-        .. image:: 
https://readthedocs.org/projects/automat/badge/?version=latest
-           :target: http://automat.readthedocs.io/en/latest/
-           :alt: Documentation Status
-        
-        
-        .. 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).
-        
-        Read more here, or on `Read the Docs 
<https://automat.readthedocs.io/>`_\ , or watch the following videos for an 
overview and presentation
-        
-        Overview and presentation by **Glyph Lefkowitz** at the first talk of 
the first Pyninsula meetup, on February 21st, 2017:
-        
-        .. image:: https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg
-           :target: https://www.youtube.com/watch?v=0wOZBpD1VVk
-           :alt: Glyph Lefkowitz - Automat - Pyninsula #0
-        
-        
-        Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
-        
-        .. image:: https://img.youtube.com/vi/TedUKXhu9kE/0.jpg
-           :target: https://www.youtube.com/watch?v=TedUKXhu9kE
-           :alt: Clinton Roy - State Machines - Pycon Australia 2017
-        
-        
-        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."
-        
-        ``dont_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
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
@@ -479,3 +19,4 @@
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Provides-Extra: visualize
+License-File: LICENSE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/Automat.egg-info/SOURCES.txt 
new/Automat-22.10.0/Automat.egg-info/SOURCES.txt
--- old/Automat-20.2.0/Automat.egg-info/SOURCES.txt     2020-02-16 
20:36:16.000000000 +0100
+++ new/Automat-22.10.0/Automat.egg-info/SOURCES.txt    2022-10-29 
09:03:31.000000000 +0200
@@ -2,9 +2,11 @@
 .travis.yml
 LICENSE
 README.md
+mypy.ini
 setup.cfg
 setup.py
 tox.ini
+.github/workflows/ci.yml
 Automat.egg-info/PKG-INFO
 Automat.egg-info/SOURCES.txt
 Automat.egg-info/dependency_links.txt
@@ -30,7 +32,7 @@
 docs/conf.py
 docs/debugging.rst
 docs/index.rst
-docs/make.bat
+docs/typing.rst
 docs/visualize.rst
 docs/_static/mystate.machine.MyMachine._machine.dot.png
 docs/examples/automat_example.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/Automat.egg-info/entry_points.txt 
new/Automat-22.10.0/Automat.egg-info/entry_points.txt
--- old/Automat-20.2.0/Automat.egg-info/entry_points.txt        2020-02-16 
20:36:16.000000000 +0100
+++ new/Automat-22.10.0/Automat.egg-info/entry_points.txt       2022-10-29 
09:03:31.000000000 +0200
@@ -1,3 +1,2 @@
 [console_scripts]
 automat-visualize = automat._visualize:tool
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/PKG-INFO new/Automat-22.10.0/PKG-INFO
--- old/Automat-20.2.0/PKG-INFO 2020-02-16 20:36:16.000000000 +0100
+++ new/Automat-22.10.0/PKG-INFO        2022-10-29 09:03:31.956159000 +0200
@@ -1,472 +1,12 @@
 Metadata-Version: 2.1
 Name: Automat
-Version: 20.2.0
+Version: 22.10.0
 Summary: Self-service finite-state machines for the programmer on the go.
 Home-page: https://github.com/glyph/Automat
 Author: Glyph
 Author-email: gl...@twistedmatrix.com
 License: MIT
-Description: 
-        Automat
-        =======
-        
-        
-        .. image:: 
https://readthedocs.org/projects/automat/badge/?version=latest
-           :target: http://automat.readthedocs.io/en/latest/
-           :alt: Documentation Status
-        
-        
-        .. 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).
-        
-        Read more here, or on `Read the Docs 
<https://automat.readthedocs.io/>`_\ , or watch the following videos for an 
overview and presentation
-        
-        Overview and presentation by **Glyph Lefkowitz** at the first talk of 
the first Pyninsula meetup, on February 21st, 2017:
-        
-        .. image:: https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg
-           :target: https://www.youtube.com/watch?v=0wOZBpD1VVk
-           :alt: Glyph Lefkowitz - Automat - Pyninsula #0
-        
-        
-        Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
-        
-        .. image:: https://img.youtube.com/vi/TedUKXhu9kE/0.jpg
-           :target: https://www.youtube.com/watch?v=TedUKXhu9kE
-           :alt: Clinton Roy - State Machines - Pycon Australia 2017
-        
-        
-        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."
-        
-        ``dont_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
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
@@ -479,3 +19,4 @@
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Provides-Extra: visualize
+License-File: LICENSE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/README.md 
new/Automat-22.10.0/README.md
--- old/Automat-20.2.0/README.md        2020-02-16 20:33:10.000000000 +0100
+++ new/Automat-22.10.0/README.md       2022-10-29 08:56:12.000000000 +0200
@@ -11,10 +11,10 @@
 
 Read more here, or on [Read the Docs](https://automat.readthedocs.io/), or 
watch the following videos for an overview and presentation
 
-Overview and presentation by **Glyph Lefkowitz** at the first talk of the 
first Pyninsula meetup, on February 21st, 2017:
+Overview and presentation by **Glyph Lefkowitz** at the first talk of the 
first Pyninsula meetup, on February 21st, 2017:  
 [![Glyph Lefkowitz - Automat - Pyninsula 
#0](https://img.youtube.com/vi/0wOZBpD1VVk/0.jpg)](https://www.youtube.com/watch?v=0wOZBpD1VVk)
 
-Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:
+Presentation by **Clinton Roy** at PyCon Australia, on August 6th 2017:  
 [![Clinton Roy - State Machines - Pycon Australia 
2017](https://img.youtube.com/vi/TedUKXhu9kE/0.jpg)](https://www.youtube.com/watch?v=TedUKXhu9kE)
 
 ### Why use state machines? ###
@@ -47,8 +47,8 @@
 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/).
+in more detail [in this excellent article by Jean-Paul
+Calderone](https://web.archive.org/web/20160507053658/https://clusterhq.com/2013/12/05/what-is-a-state-machine/).
 
 ### What makes Automat different? ###
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/automat/_introspection.py 
new/Automat-22.10.0/automat/_introspection.py
--- old/Automat-20.2.0/automat/_introspection.py        2020-02-16 
20:33:10.000000000 +0100
+++ new/Automat-22.10.0/automat/_introspection.py       2022-06-11 
01:31:24.000000000 +0200
@@ -6,6 +6,8 @@
 
 
 def copycode(template, changes):
+    if hasattr(code, "replace"):
+        return template.replace(**{"co_" + k : v for k, v in changes.items()})
     names = [
         "argcount", "nlocals", "stacksize", "flags", "code", "consts",
         "names", "varnames", "filename", "name", "firstlineno", "lnotab",
@@ -23,7 +25,6 @@
     return code(*values)
 
 
-
 def copyfunction(template, funcchanges, codechanges):
     names = [
         "globals", "name", "defaults", "closure",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/automat/_methodical.py 
new/Automat-22.10.0/automat/_methodical.py
--- old/Automat-20.2.0/automat/_methodical.py   2020-02-16 20:33:10.000000000 
+0100
+++ new/Automat-22.10.0/automat/_methodical.py  2022-06-12 06:18:47.000000000 
+0200
@@ -4,15 +4,9 @@
 from functools import wraps
 from itertools import count
 
-try:
-    # Python 3
-    from inspect import getfullargspec as getArgsSpec
-except ImportError:
-    # Python 2
-    from inspect import getargspec as getArgsSpec
+from inspect import getfullargspec as getArgsSpec
 
 import attr
-import six
 
 from ._core import Transitioner, Automaton
 from ._introspection import preserveName
@@ -36,14 +30,14 @@
     return ArgSpec(
         args=tuple(spec.args),
         varargs=spec.varargs,
-        varkw=spec.varkw if six.PY3 else spec.keywords,
+        varkw=spec.varkw,
         defaults=spec.defaults if spec.defaults else (),
-        kwonlyargs=tuple(spec.kwonlyargs) if six.PY3 else (),
+        kwonlyargs=tuple(spec.kwonlyargs),
         kwonlydefaults=(
             tuple(spec.kwonlydefaults.items())
             if spec.kwonlydefaults else ()
-        ) if six.PY3 else (),
-        annotations=tuple(spec.annotations.items()) if six.PY3 else (),
+        ),
+        annotations=tuple(spec.annotations.items()),
     )
 
 
@@ -91,7 +85,7 @@
     method = attr.ib()
     serialized = attr.ib(repr=False)
 
-    def upon(self, input, enter, outputs, collector=list):
+    def upon(self, input, enter=None, outputs=None, collector=list):
         """
         Declare a state transition within the 
:class:`automat.MethodicalMachine`
         associated with this :class:`automat.MethodicalState`:
@@ -110,6 +104,10 @@
         :raises ValueError: if the state transition from `self` via `input`
             has already been defined.
         """
+        if enter is None:
+            enter = self
+        if outputs is None:
+            outputs = []
         inputArgs = _getArgNames(input.argSpec)
         for output in outputs:
             outputArgs = _getArgNames(output.argSpec)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/automat/_test/test_discover.py 
new/Automat-22.10.0/automat/_test/test_discover.py
--- old/Automat-20.2.0/automat/_test/test_discover.py   2018-01-11 
10:16:33.000000000 +0100
+++ new/Automat-22.10.0/automat/_test/test_discover.py  2022-06-12 
06:18:47.000000000 +0200
@@ -6,9 +6,6 @@
 import tempfile
 from unittest import skipIf, TestCase
 
-import six
-
-
 def isTwistedInstalled():
     try:
         __import__('twisted')
@@ -43,7 +40,7 @@
         super(_WritesPythonModules, self).tearDown()
 
         sys.path[:] = self.savedSysPath
-        modulesToDelete = six.viewkeys(sys.modules) - self.originalSysModules
+        modulesToDelete = sys.modules.keys() - self.originalSysModules
         for module in modulesToDelete:
             del sys.modules[module]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/automat/_test/test_methodical.py 
new/Automat-22.10.0/automat/_test/test_methodical.py
--- old/Automat-20.2.0/automat/_test/test_methodical.py 2018-06-04 
07:39:34.000000000 +0200
+++ new/Automat-22.10.0/automat/_test/test_methodical.py        2022-02-17 
08:13:25.000000000 +0100
@@ -365,6 +365,50 @@
             self.assertIn("nameOfInput", str(cm.exception))
             self.assertIn("outputThatDoesntMatch", str(cm.exception))
 
+    def test_stateLoop(self):
+        """
+        It is possible to write a self-loop by omitting "enter"
+        """
+        class Mechanism(object):
+            m = MethodicalMachine()
+            @m.input()
+            def input(self):
+                "an input"
+            @m.input()
+            def say_hi(self):
+                "an input"
+            @m.output()
+            def _start_say_hi(self):
+                return "hi"
+            @m.state(initial=True)
+            def start(self):
+                "a state"
+            def said_hi(self):
+                "a state with no inputs"
+            start.upon(input, outputs=[])
+            start.upon(say_hi, outputs=[_start_say_hi])
+        a_mechanism = Mechanism()
+        [a_greeting] = a_mechanism.say_hi()
+        self.assertEqual(a_greeting, "hi")
+
+
+    def test_defaultOutputs(self):
+        """
+        It is possible to write a transition with no outputs
+        """
+        class Mechanism(object):
+            m = MethodicalMachine()
+            @m.input()
+            def finish(self):
+                "final transition"
+            @m.state(initial=True)
+            def start(self):
+                "a start state"
+            @m.state()
+            def finished(self):
+                "a final state"
+            start.upon(finish, enter=finished)
+        Mechanism().finish()
 
     def test_getArgNames(self):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/automat/_test/test_visualize.py 
new/Automat-22.10.0/automat/_test/test_visualize.py
--- old/Automat-20.2.0/automat/_test/test_visualize.py  2018-01-11 
10:16:33.000000000 +0100
+++ new/Automat-22.10.0/automat/_test/test_visualize.py 2021-03-09 
08:19:39.000000000 +0100
@@ -62,6 +62,7 @@
 
 
 @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.")
+@skipIf(not isTwistedInstalled(), "Twisted is not installed.")
 class ElementMakerTests(TestCase):
     """
     L{elementMaker} generates HTML representing the specified element.
@@ -134,6 +135,7 @@
 
 
 @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.")
+@skipIf(not isTwistedInstalled(), "Twisted is not installed.")
 class TableMakerTests(TestCase):
     """
     Tests that ensure L{tableMaker} generates HTML tables usable as
@@ -214,6 +216,7 @@
 
 @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.")
 @skipIf(not isGraphvizInstalled(), "Graphviz tools are not installed.")
+@skipIf(not isTwistedInstalled(), "Twisted is not installed.")
 class IntegrationTests(TestCase):
     """
     Tests which make sure Graphviz can understand the output produced by
@@ -232,6 +235,7 @@
 
 
 @skipIf(not isGraphvizModuleInstalled(), "Graphviz module is not installed.")
+@skipIf(not isTwistedInstalled(), "Twisted is not installed.")
 class SpotChecks(TestCase):
     """
     Tests to make sure that the output contains salient features of the machine
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/docs/about.rst 
new/Automat-22.10.0/docs/about.rst
--- old/Automat-20.2.0/docs/about.rst   2018-06-04 07:39:34.000000000 +0200
+++ new/Automat-22.10.0/docs/about.rst  2022-02-17 08:13:25.000000000 +0100
@@ -410,6 +410,10 @@
 
 .. code-block:: python
 
+    from operator import itemgetter
+
+    first = itemgetter(0)
+
     class LightSwitch(object):
         _machine = MethodicalMachine()
 
@@ -418,16 +422,21 @@
         @_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)
+
+        on_state.upon(
+            query_power, enter=on_state, outputs=[_is_powered], collector=first
+        )
+        off_state.upon(
+            query_power, enter=off_state, outputs=[_not_powered], 
collector=first
+        )
 
 
 To save the state, we have the `MethodicalMachine.serializer()` method.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/docs/index.rst 
new/Automat-22.10.0/docs/index.rst
--- old/Automat-20.2.0/docs/index.rst   2018-06-04 07:39:34.000000000 +0200
+++ new/Automat-22.10.0/docs/index.rst  2022-02-17 08:13:25.000000000 +0100
@@ -55,3 +55,4 @@
    visualize
    api
    debugging
+   typing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/docs/make.bat 
new/Automat-22.10.0/docs/make.bat
--- old/Automat-20.2.0/docs/make.bat    2018-01-11 10:16:33.000000000 +0100
+++ new/Automat-22.10.0/docs/make.bat   1970-01-01 01:00:00.000000000 +0100
@@ -1,36 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-       set SPHINXBUILD=python -msphinx
-)
-set SOURCEDIR=.
-set BUILDDIR=_build
-set SPHINXPROJ=automat
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-       echo.
-       echo.The Sphinx module was not found. Make sure you have Sphinx 
installed,
-       echo.then set the SPHINXBUILD environment variable to point to the full
-       echo.path of the 'sphinx-build' executable. Alternatively you may add 
the
-       echo.Sphinx directory to PATH.
-       echo.
-       echo.If you don't have Sphinx installed, grab it from
-       echo.http://sphinx-doc.org/
-       exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/docs/typing.rst 
new/Automat-22.10.0/docs/typing.rst
--- old/Automat-20.2.0/docs/typing.rst  1970-01-01 01:00:00.000000000 +0100
+++ new/Automat-22.10.0/docs/typing.rst 2022-02-17 08:13:25.000000000 +0100
@@ -0,0 +1,95 @@
+Static Typing
+--------------
+
+When writing an output for a given state,
+you can assume the finite state machine will be in that state.
+This might mean that specific object attributes will have values
+of speciifc types.
+Those attributes might,
+in general,
+be of some :code:`Union` type:
+frequently,
+an :code:`Option` type
+(which is a :code:`Union[T, None]`).
+
+It is an *anti-pattern* to check for these things inside the output.
+The reason for a state machine is for the outputs to avoid checking.
+However,
+if the output is type annotated,
+often :code:`mypy`
+will complain that it cannot validate the types.
+The recommended solution is to
+:code:`assert`
+the types inside the code.
+This aligns
+the assumptions
+:code:`mypy`
+makes
+with the assumptions
+:code:`automat`
+makes.
+
+For example,
+consider the following:
+
+.. code::
+
+    import attr
+    import automat
+    from typing import Optional
+
+    @attr.s(auto_attribs=True)
+    class MaybeValue:
+        _machine = automat.MethodicalMachine()
+        _value: Optional[float] = attr.ib(default=None)
+
+        @_machine.input()
+        def set_value(self, value: float) -> None:
+            "The value has been measured"
+
+        @_machine.input()
+        def get_value(self) -> float:
+            "Return the value if it has been measured"
+
+        @_machine.output()
+        def _set_value_when_unset(self, value: float) -> None:
+            self._value = value
+
+        @_machine.output()
+        def _get_value_when_set(self) -> float:
+            """mypy will complain here:
+
+            Incompatible return value type
+            (got "Optional[float]", expected "float")
+            """
+            return self._value
+
+        @_machine.state()
+        def value_is_set(self):
+            "The value is set"
+
+        @_machine.state(initial=True)
+        def value_is_unset(self):
+            "The value is not set"
+
+        value_is_unset.upon(
+            set_value,
+            enter=value_is_set,
+            outputs=[_set_value_when_unset],
+            collector=lambda x: None,
+        )
+        value_is_set.upon(
+            get_value,
+            enter=value_is_set,
+            outputs=[_get_value_when_set],
+            collector=lambda x: next(iter(x)),
+        )
+
+In this case
+starting
+:code:`_get_value_when_set`
+with a line
+:code:`assert self._value is not None`
+will satisfy
+:code:`mypy`.
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/mypy.ini new/Automat-22.10.0/mypy.ini
--- old/Automat-20.2.0/mypy.ini 1970-01-01 01:00:00.000000000 +0100
+++ new/Automat-22.10.0/mypy.ini        2022-10-29 08:56:09.000000000 +0200
@@ -0,0 +1,3 @@
+[mypy]
+[mypy-graphviz.*]
+ignore_missing_imports = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/setup.py new/Automat-22.10.0/setup.py
--- old/Automat-20.2.0/setup.py 2020-02-16 20:33:10.000000000 +0100
+++ new/Automat-22.10.0/setup.py        2022-06-12 06:18:47.000000000 +0200
@@ -4,14 +4,6 @@
 
 from setuptools import setup, find_packages
 
-try:
-    from m2r import parse_from_file
-    long_description = parse_from_file('README.md')
-except(IOError, ImportError):
-    print("\n\n!!! m2r not found, long_description is bad, don't upload this 
to PyPI !!!\n\n")
-    import io
-    long_description = io.open('README.md', encoding="utf-8").read()
-
 setup(
     name='Automat',
     use_scm_version=True,
@@ -19,12 +11,12 @@
     description="""
     Self-service finite-state machines for the programmer on the go.
     """.strip(),
-    long_description=long_description,
+    readme='README.md',
     packages=find_packages(exclude=[]),
     package_dir={'automat': 'automat'},
     setup_requires=[
+        'wheel',
         'setuptools-scm',
-        'm2r',
     ],
     install_requires=[
         "attrs>=19.2.0",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Automat-20.2.0/tox.ini new/Automat-22.10.0/tox.ini
--- old/Automat-20.2.0/tox.ini  2020-02-16 20:33:10.000000000 +0100
+++ new/Automat-22.10.0/tox.ini 2022-10-29 08:56:12.000000000 +0200
@@ -1,5 +1,5 @@
 [tox]
-envlist = 
coverage-clean,{py27,pypy,py35,py36,py38}-{extras,noextras},coverage-report
+envlist = 
lint,mypy,coverage-clean,{pypy3,py38,py310}-{extras,noextras},coverage-report
 
 [testenv]
 deps =
@@ -10,35 +10,55 @@
 
 commands =
     coverage run --parallel --source automat -m py.test automat/_test
+depends =
+    coverage-clean
 
 [testenv:coverage-clean]
 deps = coverage
 skip_install = true
 commands = coverage erase
+depends =
 
 [testenv:coverage-report]
 deps = coverage
 skip_install = true
 commands =
     coverage combine
+    coverage xml
     coverage report -m
+depends =
+    {pypy3,py38,py310}-{extras,noextras}
 
 [testenv:benchmark]
 deps = pytest-benchmark
 commands = pytest --benchmark-only benchmark/
 
-[testenv:py27-benchmark]
+[testenv:py310-benchmark]
 deps = {[testenv:benchmark]deps}
 commands = {[testenv:benchmark]commands}
 
-[testenv:py35-benchmark]
-deps = {[testenv:benchmark]deps}
-commands = {[testenv:benchmark]commands}
+[testenv:lint]
+deps = black
+commands = black --check automat
 
-[testenv:py36-benchmark]
-deps = {[testenv:benchmark]deps}
-commands = {[testenv:benchmark]commands}
+[testenv:mypy]
+deps =
+    mypy
+    graphviz>=0.4.9
+    Twisted>=16.2.0
 
-[testenv:pypy-benchmark]
+commands = mypy automat
+
+[testenv:pypy3-benchmark]
 deps = {[testenv:benchmark]deps}
 commands = {[testenv:benchmark]commands}
+
+[testenv:docs]
+usedevelop = True
+changedir = docs
+deps =
+    sphinx
+    sphinx_rtd_theme
+commands =
+    sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
+basepython = python3.8

Reply via email to