METADATA 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. Metadata-Version: 2.1
  2. Name: Automat
  3. Version: 24.8.1
  4. Summary: Self-service finite-state machines for the programmer on the go.
  5. Author-email: Glyph <code@glyph.im>
  6. License: Copyright (c) 2014
  7. Rackspace
  8. Permission is hereby granted, free of charge, to any person obtaining
  9. a copy of this software and associated documentation files (the
  10. "Software"), to deal in the Software without restriction, including
  11. without limitation the rights to use, copy, modify, merge, publish,
  12. distribute, sublicense, and/or sell copies of the Software, and to
  13. permit persons to whom the Software is furnished to do so, subject to
  14. the following conditions:
  15. The above copyright notice and this permission notice shall be
  16. included in all copies or substantial portions of the Software.
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  21. LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  22. OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  23. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  24. Project-URL: Documentation, https://automat.readthedocs.io/
  25. Project-URL: Source, https://github.com/glyph/automat/
  26. Keywords: fsm,state machine,automata
  27. Classifier: Intended Audience :: Developers
  28. Classifier: License :: OSI Approved :: MIT License
  29. Classifier: Operating System :: OS Independent
  30. Classifier: Programming Language :: Python
  31. Classifier: Programming Language :: Python :: 3
  32. Classifier: Programming Language :: Python :: 3.8
  33. Classifier: Programming Language :: Python :: 3.9
  34. Classifier: Programming Language :: Python :: 3.10
  35. Classifier: Programming Language :: Python :: 3.11
  36. Classifier: Programming Language :: Python :: 3.12
  37. Requires-Python: >=3.8
  38. Description-Content-Type: text/markdown
  39. License-File: LICENSE
  40. Requires-Dist: typing-extensions; python_version < "3.10"
  41. Provides-Extra: visualize
  42. Requires-Dist: graphviz>0.5.1; extra == "visualize"
  43. Requires-Dist: Twisted>=16.1.1; extra == "visualize"
  44. # Automat #
  45. [![Documentation Status](https://readthedocs.org/projects/automat/badge/?version=latest)](http://automat.readthedocs.io/en/latest/)
  46. [![Build Status](https://github.com/glyph/automat/actions/workflows/ci.yml/badge.svg?branch=trunk)](https://github.com/glyph/automat/actions/workflows/ci.yml?query=branch%3Atrunk)
  47. [![Coverage Status](http://codecov.io/github/glyph/automat/coverage.svg?branch=trunk)](http://codecov.io/github/glyph/automat?branch=trunk)
  48. ## Self-service finite-state machines for the programmer on the go. ##
  49. Automat is a library for concise, idiomatic Python expression of finite-state
  50. automata (particularly deterministic finite-state transducers).
  51. Read more here, or on [Read the Docs](https://automat.readthedocs.io/), or watch the following videos for an overview and presentation
  52. ### Why use state machines? ###
  53. Sometimes you have to create an object whose behavior varies with its state,
  54. but still wishes to present a consistent interface to its callers.
  55. For example, let's say you're writing the software for a coffee machine. It
  56. has a lid that can be opened or closed, a chamber for water, a chamber for
  57. coffee beans, and a button for "brew".
  58. There are a number of possible states for the coffee machine. It might or
  59. might not have water. It might or might not have beans. The lid might be open
  60. or closed. The "brew" button should only actually attempt to brew coffee in
  61. one of these configurations, and the "open lid" button should only work if the
  62. coffee is not, in fact, brewing.
  63. With diligence and attention to detail, you can implement this correctly using
  64. a collection of attributes on an object; `hasWater`, `hasBeans`, `isLidOpen`
  65. and so on. However, you have to keep all these attributes consistent. As the
  66. coffee maker becomes more complex - perhaps you add an additional chamber for
  67. flavorings so you can make hazelnut coffee, for example - you have to keep
  68. adding more and more checks and more and more reasoning about which
  69. combinations of states are allowed.
  70. Rather than adding tedious `if` checks to every single method to make sure that
  71. each of these flags are exactly what you expect, you can use a state machine to
  72. ensure that if your code runs at all, it will be run with all the required
  73. values initialized, because they have to be called in the order you declare
  74. them.
  75. You can read about state machines and their advantages for Python programmers
  76. in more detail [in this excellent article by Jean-Paul
  77. Calderone](https://web.archive.org/web/20160507053658/https://clusterhq.com/2013/12/05/what-is-a-state-machine/).
  78. ### What makes Automat different? ###
  79. There are
  80. [dozens of libraries on PyPI implementing state machines](https://pypi.org/search/?q=finite+state+machine).
  81. So it behooves me to say why yet another one would be a good idea.
  82. Automat is designed around this principle: while organizing your code around
  83. state machines is a good idea, your callers don't, and shouldn't have to, care
  84. that you've done so. In Python, the "input" to a stateful system is a method
  85. call; the "output" may be a method call, if you need to invoke a side effect,
  86. or a return value, if you are just performing a computation in memory. Most
  87. other state-machine libraries require you to explicitly create an input object,
  88. provide that object to a generic "input" method, and then receive results,
  89. sometimes in terms of that library's interfaces and sometimes in terms of
  90. classes you define yourself.
  91. For example, a snippet of the coffee-machine example above might be implemented
  92. as follows in naive Python:
  93. ```python
  94. class CoffeeMachine(object):
  95. def brewButton(self) -> None:
  96. if self.hasWater and self.hasBeans and not self.isLidOpen:
  97. self.heatTheHeatingElement()
  98. # ...
  99. ```
  100. With Automat, you'd begin with a `typing.Protocol` that describes all of your
  101. inputs:
  102. ```python
  103. from typing import Protocol
  104. class CoffeeBrewer(Protocol):
  105. def brewButton(self) -> None:
  106. "The user pressed the 'brew' button."
  107. def putInBeans(self) -> None:
  108. "The user put in some beans."
  109. ```
  110. We'll then need a concrete class to contain the shared core of state shared
  111. among the different states:
  112. ```python
  113. from dataclasses import dataclass
  114. @dataclass
  115. class BrewerCore:
  116. heatingElement: HeatingElement
  117. ```
  118. Next, we need to describe our state machine, including all of our states. For
  119. simplicity's sake let's say that the only two states are `noBeans` and
  120. `haveBeans`:
  121. ```python
  122. from automat import TypeMachineBuilder
  123. builder = TypeMachineBuilder(CoffeeBrewer, BrewerCore)
  124. noBeans = builder.state("noBeans")
  125. haveBeans = builder.state("haveBeans")
  126. ```
  127. Next we can describe a simple transition; when we put in beans, we move to the
  128. `haveBeans` state, with no other behavior.
  129. ```python
  130. # When we don't have beans, upon putting in beans, we will then have beans
  131. noBeans.upon(CoffeeBrewer.putInBeans).to(haveBeans).returns(None)
  132. ```
  133. And then another transition that we describe with a decorator, one that *does*
  134. have some behavior, that needs to heat up the heating element to brew the
  135. coffee:
  136. ```python
  137. @haveBeans.upon(CoffeeBrewer.brewButton).to(noBeans)
  138. def heatUp(inputs: CoffeeBrewer, core: BrewerCore) -> None:
  139. """
  140. When we have beans, upon pressing the brew button, we will then not have
  141. beans any more (as they have been entered into the brewing chamber) and
  142. our output will be heating the heating element.
  143. """
  144. print("Brewing the coffee...")
  145. core.heatingElement.turnOn()
  146. ```
  147. Then we finalize the state machine by building it, which gives us a callable
  148. that takes a `BrewerCore` and returns a synthetic `CoffeeBrewer`
  149. ```python
  150. newCoffeeMachine = builder.build()
  151. ```
  152. ```python
  153. >>> coffee = newCoffeeMachine(BrewerCore(HeatingElement()))
  154. >>> machine.putInBeans()
  155. >>> machine.brewButton()
  156. Brewing the coffee...
  157. ```
  158. All of the *inputs* are provided by calling them like methods, all of the
  159. *output behaviors* are automatically invoked when they are produced according
  160. to the outputs specified to `upon` and all of the states are simply opaque
  161. tokens.