File: PKG-INFO

package info (click to toggle)
automat 20.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 400 kB
  • sloc: python: 1,909; makefile: 16; sh: 2
file content (481 lines) | stat: -rw-r--r-- 21,432 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
Metadata-Version: 2.1
Name: Automat
Version: 20.2.0
Summary: Self-service finite-state machines for the programmer on the go.
Home-page: https://github.com/glyph/Automat
Author: Glyph
Author-email: glyph@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
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Provides-Extra: visualize