banner



Invalid Block Tag On Did You Forget To Register Or Load This Tag?

Quickstart

They say a good example is worth 100 pages of API documentation, a meg directives, or a thousand words.

Well, "they" probably prevarication... but here'south an instance anyhow:

          from          transitions          import          Auto          import          random          class          NarcolepticSuperhero          (          object          ):          # Ascertain some states. Nearly of the time, narcoleptic superheroes are just similar          # everyone else. Except for...          states          =          [          'comatose'          ,          'hanging out'          ,          'hungry'          ,          'sweaty'          ,          'saving the world'          ]          def          __init__          (          self          ,          name          ):          # No anonymous superheroes on my spotter! Every narcoleptic superhero gets          # a proper name. Any name at all. SleepyMan. SlumberGirl. You go the idea.          self          .          name          =          name          # What have nosotros accomplished today?          self          .          kittens_rescued          =          0          # Initialize the state machine          self          .          motorcar          =          Machine          (          model          =          cocky          ,          states          =          NarcolepticSuperhero          .          states          ,          initial          =          'asleep'          )          # Add some transitions. Nosotros could likewise define these using a static listing of          # dictionaries, as we did with states above, and then pass the list to          # the Car initializer as the transitions= statement.          # At some point, every superhero must rise and shine.          cocky          .          machine          .          add_transition          (          trigger          =          'wake_up'          ,          source          =          'asleep'          ,          dest          =          'hanging out'          )          # Superheroes need to go on in shape.          cocky          .          auto          .          add_transition          (          'work_out'          ,          'hanging out'          ,          'hungry'          )          # Those calories won't replenish themselves!          self          .          machine          .          add_transition          (          'eat'          ,          'hungry'          ,          'hanging out'          )          # Superheroes are always on call. E'er. But they're not ever          # dressed in work-appropriate habiliment.          self          .          machine          .          add_transition          (          'distress_call'          ,          '*'          ,          'saving the earth'          ,          before          =          'change_into_super_secret_costume'          )          # When they get off work, they're all sweaty and disgusting. But before          # they do anything else, they take to meticulously log their latest          # escapades. Because the legal department says so.          self          .          machine          .          add_transition          (          'complete_mission'          ,          'saving the world'          ,          'sweaty'          ,          afterward          =          'update_journal'          )          # Sweat is a disorder that can be remedied with water.          # Unless y'all've had a particularly long twenty-four hours, in which case... bed time!          self          .          machine          .          add_transition          (          'clean_up'          ,          'sweaty'          ,          'asleep'          ,          conditions          =          [          'is_exhausted'          ])          self          .          machine          .          add_transition          (          'clean_up'          ,          'sweaty'          ,          'hanging out'          )          # Our NarcolepticSuperhero tin fall comatose at pretty much whatsoever time.          self          .          machine          .          add_transition          (          'nap'          ,          '*'          ,          'asleep'          )          def          update_journal          (          cocky          ):          """ Dear Diary, today I saved Mr. Whiskers. Again. """          cocky          .          kittens_rescued          +=          1          @belongings          def          is_exhausted          (          cocky          ):          """ Basically a coin toss. """          return          random          .          random          ()          <          0.5          def          change_into_super_secret_costume          (          self          ):          print          (          "Dazzler, eh?"          )        

There, now you lot've baked a land motorcar into NarcolepticSuperhero. Allow'south have him/her/it out for a spin...

          >>>          batman          =          NarcolepticSuperhero          (          "Batman"          )          >>>          batman          .          state          'asleep'          >>>          batman          .          wake_up          ()          >>>          batman          .          state          'hanging out'          >>>          batman          .          nap          ()          >>>          batman          .          state          'asleep'          >>>          batman          .          clean_up          ()          MachineError          :          "Tin can't trigger event clean_up from land comatose!"          >>>          batman          .          wake_up          ()          >>>          batman          .          work_out          ()          >>>          batman          .          country          'hungry'          # Batman withal hasn't done anything useful...          >>>          batman          .          kittens_rescued          0          # We now accept you alive to the scene of a horrific kitten entreement...          >>>          batman          .          distress_call          ()          'Dazzler, eh?'          >>>          batman          .          state          'saving the world'          # Back to the crib.          >>>          batman          .          complete_mission          ()          >>>          batman          .          country          'sweaty'          >>>          batman          .          clean_up          ()          >>>          batman          .          state          'asleep'          # Besides tired to shower!          # Another productive day, Alfred.          >>>          batman          .          kittens_rescued          ane        

While we cannot read the heed of the actual batman, we surely can visualize the electric current land of our NarcolepticSuperhero.

batman diagram

Have a wait at the Diagrams extensions if yous want to know how.

The not-quickstart

Basic initialization

Getting a state machine up and running is pretty uncomplicated. Allow's say yous have the object lump (an instance of class Matter), and you want to manage its states:

          course          Matter          (          object          ):          laissez passer          lump          =          Matter          ()        

You can initialize a (minimal) working state machine bound to lump like this:

          from          transitions          import          Machine          machine          =          Machine          (          model          =          lump          ,          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ],          initial          =          'solid'          )          # Lump at present has state!          lump          .          land          >>>          'solid'        

I say "minimal", because while this state machine is technically operational, information technology doesn't actually do annihilation. It starts in the 'solid' state, simply won't ever move into another state, because no transitions are defined... however!

Permit'due south try again.

          # The states          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ]          # And some transitions between states. We're lazy, and then we'll leave out          # the changed phase transitions (freezing, condensation, etc.).          transitions          =          [          {          'trigger'          :          'melt'          ,          'source'          :          'solid'          ,          'dest'          :          'liquid'          },          {          'trigger'          :          'evaporate'          ,          'source'          :          'liquid'          ,          'dest'          :          'gas'          },          {          'trigger'          :          'sublimate'          ,          'source'          :          'solid'          ,          'dest'          :          'gas'          },          {          'trigger'          :          'ionize'          ,          'source'          :          'gas'          ,          'dest'          :          'plasma'          }          ]          # Initialize          car          =          Car          (          lump          ,          states          =          states          ,          transitions          =          transitions          ,          initial          =          'liquid'          )          # Now lump maintains state...          lump          .          state          >>>          'liquid'          # And that country can change...          lump          .          evaporate          ()          lump          .          land          >>>          'gas'          lump          .          trigger          (          'ionize'          )          lump          .          state          >>>          'plasma'        

Find the shiny new methods fastened to the Affair instance (evaporate(), ionize(), etc.). Each method triggers the corresponding transition. You lot don't have to explicitly define these methods anywhere; the name of each transition is bound to the model passed to the Machine initializer (in this example, lump). To be more precise, your model should not already contain methods with the same name as event triggers since transitions volition just attach convenience methods to your model if the spot is not already taken. If you want to modify that behaviour, have a look at the FAQ. Furthermore, in that location is a method called trigger at present attached to your model (if it hasn't been there before). This method lets you execute transitions by name in example dynamic triggering is required.

States

The soul of any good state machine (and of many bad ones, no doubt) is a set of states. Above, we defined the valid model states past passing a list of strings to the Machine initializer. But internally, states are really represented equally State objects.

Yous tin can initialize and change States in a number of ways. Specifically, you can:

  • pass a string to the Car initializer giving the name(s) of the land(s), or
  • directly initialize each new State object, or
  • pass a lexicon with initialization arguments

The following snippets illustrate several ways to achieve the same goal:

          # import Machine and Land course          from          transitions          import          Machine          ,          State          # Create a list of three states to pass to the Automobile          # initializer. We tin can mix types; in this case, nosotros          # laissez passer one State, one string, and i dict.          states          =          [          Land          (          name          =          'solid'          ),          'liquid'          ,          {          'proper name'          :          'gas'          }          ]          machine          =          Machine          (          lump          ,          states          )          # This alternative example illustrates more explicit          # improver of states and state callbacks, simply the net          # issue is identical to the in a higher place.          automobile          =          Machine          (          lump          )          solid          =          State          (          'solid'          )          liquid          =          State          (          'liquid'          )          gas          =          Land          (          'gas'          )          car          .          add_states          ([          solid          ,          liquid          ,          gas          ])        

States are initialized in one case when added to the car and will persist until they are removed from it. In other words: if you alter the attributes of a state object, this modify will NOT exist reset the next time yous enter that state. Take a expect at how to extend land features in instance you require some other behaviour.

Callbacks

A State can also be associated with a list of enter and get out callbacks, which are called whenever the country car enters or leaves that land. You can specify callbacks during initialization by passing them to a State object constructor, in a state property dictionary, or add together them afterward.

For convenience, whenever a new State is added to a Machine, the methods on_enter_«land proper name» and on_exit_«land name» are dynamically created on the Machine (not on the model!), which permit you to dynamically add together new enter and leave callbacks afterward if you need them.

          # Our erstwhile Affair grade, now with  a couple of new methods we          # can trigger when entering or exit states.          class          Thing          (          object          ):          def          say_hello          (          cocky          ):          print          (          "hello, new state!"          )          def          say_goodbye          (          cocky          ):          print          (          "goodbye, old state!"          )          lump          =          Thing          ()          # Same states as above, but now we give StateA an exit callback          states          =          [          State          (          name          =          'solid'          ,          on_exit          =          [          'say_goodbye'          ]),          'liquid'          ,          {          'name'          :          'gas'          ,          'on_exit'          :          [          'say_goodbye'          ]}          ]          machine          =          Automobile          (          lump          ,          states          =          states          )          automobile          .          add_transition          (          'sublimate'          ,          'solid'          ,          'gas'          )          # Callbacks can also be added after initialization using          # the dynamically added on_enter_ and on_exit_ methods.          # Note that the initial call to add the callback is made          # on the Machine and not on the model.          machine          .          on_enter_gas          (          'say_hello'          )          # Test out the callbacks...          machine          .          set_state          (          'solid'          )          lump          .          sublimate          ()          >>>          'goodbye, former state!'          >>>          'hullo, new state!'        

Note that on_enter_«state name» callback volition non fire when a Machine is first initialized. For example if y'all have an on_enter_A() callback defined, and initialize the Machine with initial='A', on_enter_A() will non be fired until the side by side time you enter land A. (If you demand to brand certain on_enter_A() fires at initialization, you tin simply create a dummy initial state and then explicitly phone call to_A() inside the __init__ method.)

In addition to passing in callbacks when initializing a Land, or adding them dynamically, it's also possible to define callbacks in the model form itself, which may increase lawmaking clarity. For example:

          course          Matter          (          object          ):          def          say_hello          (          cocky          ):          print          (          "howdy, new country!"          )          def          say_goodbye          (          self          ):          impress          (          "goodbye, old state!"          )          def          on_enter_A          (          self          ):          print          (          "Nosotros've just entered state A!"          )          lump          =          Matter          ()          auto          =          Car          (          lump          ,          states          =          [          'A'          ,          'B'          ,          'C'          ])        

Now, any time lump transitions to land A, the on_enter_A() method divers in the Thing class will fire.

Checking land

You can always check the current state of the model past either:

  • inspecting the .land aspect, or
  • calling is_«land name»()

And if you want to think the bodily Country object for the electric current state, you can do that through the Machine instance's get_state() method.

          lump          .          state          >>>          'solid'          lump          .          is_gas          ()          >>>          False          lump          .          is_solid          ()          >>>          True          auto          .          get_state          (          lump          .          land          )          .          proper noun          >>>          'solid'        

If you'd similar you can choose your own land aspect name by passing the model_attribute statement while initializing the Machine. This volition also modify the name of is_«land name»() to is_«model_attribute»_«country name»() though. Similarly, auto transitions will be named to_«model_attribute»_«state name»() instead of to_«state proper noun»(). This is done to allow multiple machines to work on the same model with private state attribute names.

          lump          =          Thing          ()          motorcar          =          Machine          (          lump          ,          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ],          model_attribute          =          'matter_state'          ,          initial          =          'solid'          )          lump          .          matter_state          >>>          'solid'          # with a custom 'model_attribute', states can likewise be checked like this:          lump          .          is_matter_state_solid          ()          >>>          True          lump          .          to_matter_state_gas          ()          >>>          Truthful        

Enumerations

And so far we have seen how we tin give land names and use these names to work with our state auto. If you lot favour stricter typing and more IDE code completion (or you just can't type 'sesquipedalophobia' whatsoever longer because the word scares you) using Enumerations might be what yous are looking for:

          import          enum          # Python 2.7 users need to accept 'enum34' installed          from          transitions          import          Automobile          class          States          (          enum          .          Enum          ):          ERROR          =          0          Cerise          =          1          YELLOW          =          two          GREEN          =          3          transitions          =          [[          'proceed'          ,          States          .          RED          ,          States          .          Yellowish          ],          [          'proceed'          ,          States          .          Yellowish          ,          States          .          GREEN          ],          [          'mistake'          ,          '*'          ,          States          .          ERROR          ]]          m          =          Car          (          states          =          States          ,          transitions          =          transitions          ,          initial          =          States          .          Cherry-red          )          assert          g          .          is_RED          ()          affirm          yard          .          country          is          States          .          RED          state          =          m          .          get_state          (          States          .          Blood-red          )          # become transitions.Land object          print          (          state          .          name          )          # >>> Scarlet          grand          .          proceed          ()          m          .          proceed          ()          assert          m          .          is_GREEN          ()          thousand          .          error          ()          assert          m          .          state          is          States          .          ERROR        

You can mix enums and strings if yous like (east.g. [States.RED, 'Orangish', States.Yellow, States.GREEN]) but notation that internally, transitions will still handle states by name (enum.Enum.proper name). Thus, it is not possible to have the states 'GREEN' and States.GREEN at the same time.

Transitions

Some of the higher up examples already illustrate the apply of transitions in passing, but here we'll explore them in more detail.

As with states, each transition is represented internally as its own object – an instance of class Transition. The quickest way to initialize a set of transitions is to pass a dictionary, or list of dictionaries, to the Machine initializer. Nosotros already saw this above:

          transitions          =          [          {          'trigger'          :          'melt'          ,          'source'          :          'solid'          ,          'dest'          :          'liquid'          },          {          'trigger'          :          'evaporate'          ,          'source'          :          'liquid'          ,          'dest'          :          'gas'          },          {          'trigger'          :          'sublimate'          ,          'source'          :          'solid'          ,          'dest'          :          'gas'          },          {          'trigger'          :          'ionize'          ,          'source'          :          'gas'          ,          'dest'          :          'plasma'          }          ]          machine          =          Machine          (          model          =          Matter          (),          states          =          states          ,          transitions          =          transitions          )        

Defining transitions in dictionaries has the do good of clarity, just tin can exist cumbersome. If you're after brevity, you might choose to ascertain transitions using lists. Just brand sure that the elements in each list are in the same order as the positional arguments in the Transition initialization (i.east., trigger, source, destination, etc.).

The following list-of-lists is functionally equivalent to the list-of-dictionaries above:

          transitions          =          [          [          'melt'          ,          'solid'          ,          'liquid'          ],          [          'evaporate'          ,          'liquid'          ,          'gas'          ],          [          'sublimate'          ,          'solid'          ,          'gas'          ],          [          'ionize'          ,          'gas'          ,          'plasma'          ]          ]        

Alternatively, y'all can add together transitions to a Automobile after initialization:

          machine          =          Machine          (          model          =          lump          ,          states          =          states          ,          initial          =          'solid'          )          motorcar          .          add_transition          (          'melt'          ,          source          =          'solid'          ,          dest          =          'liquid'          )        

The trigger argument defines the name of the new triggering method that gets attached to the base model. When this method is called, it will try to execute the transition:

          >>>          lump          .          melt          ()          >>>          lump          .          state          'liquid'        

Past default, calling an invalid trigger volition raise an exception:

          >>>          lump          .          to_gas          ()          >>>          # This won't work because but objects in a solid state tin can melt          >>>          lump          .          melt          ()          transitions          .          core          .          MachineError          :          "Tin't trigger event melt from state gas!"        

This behavior is more often than not desirable, since it helps alert you to bug in your code. Simply in some cases, you might desire to silently ignore invalid triggers. You can do this by setting ignore_invalid_triggers=Truthful (either on a land-by-country basis, or globally for all states):

          >>>          # Globally suppress invalid trigger exceptions          >>>          m          =          Machine          (          lump          ,          states          ,          initial          =          'solid'          ,          ignore_invalid_triggers          =          True          )          >>>          # ...or suppress for only one grouping of states          >>>          states          =          [          'new_state1'          ,          'new_state2'          ]          >>>          m          .          add_states          (          states          ,          ignore_invalid_triggers          =          True          )          >>>          # ...or fifty-fifty just for a single country. Hither, exceptions will merely exist suppressed when the electric current state is A.          >>>          states          =          [          State          (          'A'          ,          ignore_invalid_triggers          =          Truthful          ),          'B'          ,          'C'          ]          >>>          thousand          =          Machine          (          lump          ,          states          )          >>>          # ...this can be inverted as well if but one country should raise an exception          >>>          # since the machine'south global value is non applied to a previously initialized country.          >>>          states          =          [          'A'          ,          'B'          ,          State          (          'C'          )]          # the default value for 'ignore_invalid_triggers' is False          >>>          m          =          Automobile          (          lump          ,          states          ,          ignore_invalid_triggers          =          True          )        

If yous need to know which transitions are valid from a certain state, you lot can use get_triggers:

          m.get_triggers('solid') >>> ['melt', 'sublimate'] m.get_triggers('liquid') >>> ['evaporate'] yard.get_triggers('plasma') >>> [] # y'all tin also query several states at in one case m.get_triggers('solid', 'liquid', 'gas', 'plasma') >>> ['melt', 'evaporate', 'sublimate', 'ionize']                  

Automatic transitions for all states

In addition to whatsoever transitions added explicitly, a to_«land»() method is created automatically whenever a state is added to a Machine example. This method transitions to the target state no matter which state the machine is currently in:

          lump          .          to_liquid          ()          lump          .          state          >>>          'liquid'          lump          .          to_solid          ()          lump          .          state          >>>          'solid'        

If y'all want, you tin can disable this beliefs by setting auto_transitions=Fake in the Car initializer.

Transitioning from multiple states

A given trigger can exist attached to multiple transitions, some of which can potentially begin or end in the aforementioned state. For case:

          machine          .          add_transition          (          'transmogrify'          ,          [          'solid'          ,          'liquid'          ,          'gas'          ],          'plasma'          )          machine          .          add_transition          (          'transmogrify'          ,          'plasma'          ,          'solid'          )          # This next transition will never execute          machine          .          add_transition          (          'transmogrify'          ,          'plasma'          ,          'gas'          )        

In this case, calling transmogrify() will set the model's state to 'solid' if it'southward currently 'plasma', and set information technology to 'plasma' otherwise. (Annotation that merely the beginning matching transition will execute; thus, the transition divers in the final line higher up won't do anything.)

You can also make a trigger cause a transition from all states to a particular destination past using the '*' wildcard:

          auto          .          add_transition          (          'to_liquid'          ,          '*'          ,          'liquid'          )        

Note that wildcard transitions will only utilize to states that exist at the fourth dimension of the add_transition() call. Calling a wildcard-based transition when the model is in a state added after the transition was defined will elicit an invalid transition message, and will not transition to the target state.

Reflexive transitions from multiple states

A reflexive trigger (trigger that has the aforementioned country every bit source and destination) tin can hands exist added specifying = as destination. This is handy if the same reflexive trigger should exist added to multiple states. For instance:

          automobile          .          add_transition          (          'touch'          ,          [          'liquid'          ,          'gas'          ,          'plasma'          ],          '='          ,          after          =          'change_shape'          )        

This will add reflexive transitions for all three states with touch() as trigger and with change_shape executed after each trigger.

Internal transitions

In contrast to reflexive transitions, internal transitions will never really leave the state. This ways that transition-related callbacks such as earlier or after will be candy while state-related callbacks exit or enter volition non. To define a transition to be internal, set the destination to None.

          machine          .          add_transition          (          'internal'          ,          [          'liquid'          ,          'gas'          ],          None          ,          later on          =          'change_shape'          )        

Ordered transitions

A common desire is for country transitions to follow a strict linear sequence. For instance, given states ['A', 'B', 'C'], you might want valid transitions for AB, BC, and CA (but no other pairs).

To facilitate this beliefs, Transitions provides an add_ordered_transitions() method in the Machine class:

          states          =          [          'A'          ,          'B'          ,          'C'          ]          # Encounter the "culling initialization" section for an explanation of the 1st argument to init          machine          =          Machine          (          states          =          states          ,          initial          =          'A'          )          machine          .          add_ordered_transitions          ()          car          .          next_state          ()          print          (          automobile          .          state          )          >>>          'B'          # We can also ascertain a different club of transitions          car          =          Machine          (          states          =          states          ,          initial          =          'A'          )          machine          .          add_ordered_transitions          ([          'A'          ,          'C'          ,          'B'          ])          machine          .          next_state          ()          print          (          machine          .          land          )          >>>          'C'          # Conditions can be passed to 'add_ordered_transitions' also          # If one condition is passed, it will be used for all transitions          automobile          =          Machine          (          states          =          states          ,          initial          =          'A'          )          machine          .          add_ordered_transitions          (          weather          =          'check'          )          # If a list is passed, it must contain exactly as many elements as the          # motorcar contains states (A->B, ..., X->A)          machine          =          Machine          (          states          =          states          ,          initial          =          'A'          )          machine          .          add_ordered_transitions          (          weather condition          =          [          'check_A2B'          ,          ...          ,          'check_X2A'          ])          # Conditions are always applied starting from the initial state          machine          =          Motorcar          (          states          =          states          ,          initial          =          'B'          )          motorcar          .          add_ordered_transitions          (          weather condition          =          [          'check_B2C'          ,          ...          ,          'check_A2B'          ])          # With `loop=Faux`, the transition from the last state to the first state will be omitted (east.g. C->A)          # When you also pass atmospheric condition, you need to pass one condition less (len(states)-1)          machine          =          Motorcar          (          states          =          states          ,          initial          =          'A'          )          automobile          .          add_ordered_transitions          (          loop          =          Faux          )          machine          .          next_state          ()          machine          .          next_state          ()          machine          .          next_state          ()          # transitions.cadre.MachineError: "Tin can't trigger event next_state from country C!"        

Queued transitions

The default behaviour in Transitions is to procedure events instantly. This ways events inside an on_enter method will be processed before callbacks bound to after are called.

          def          go_to_C          ():          global          motorcar          machine          .          to_C          ()          def          after_advance          ():          impress          (          "I am in state B now!"          )          def          entering_C          ():          print          (          "I am in state C now!"          )          states          =          [          'A'          ,          'B'          ,          'C'          ]          machine          =          Car          (          states          =          states          ,          initial          =          'A'          )          # we want a message when state transition to B has been completed          car          .          add_transition          (          'advance'          ,          'A'          ,          'B'          ,          after          =          after_advance          )          # call transition from state B to state C          car          .          on_enter_B          (          go_to_C          )          # we also want a message when entering state C          machine          .          on_enter_C          (          entering_C          )          automobile          .          advance          ()          >>>          'I am in state C now!'          >>>          'I am in country B now!'          # what?        

The execution guild of this example is

          fix -> before -> on_enter_B -> on_enter_C -> afterward.                  

If queued processing is enabled, a transition will be finished before the next transition is triggered:

          auto          =          Car          (          states          =          states          ,          queued          =          True          ,          initial          =          'A'          )          ...          automobile          .          advance          ()          >>>          'I am in state B now!'          >>>          'I am in state C at present!'          # That's better!        

This results in

          prepare -> earlier -> on_enter_B -> queue(to_C) -> after  -> on_enter_C.                  

Of import note: when processing events in a queue, the trigger call will always return Truthful, since there is no mode to determine at queuing time whether a transition involving queued calls will ultimately complete successfully. This is true fifty-fifty when merely a single event is processed.

          machine          .          add_transition          (          'spring'          ,          'A'          ,          'C'          ,          atmospheric condition          =          'will_fail'          )          ...          # queued=False          car          .          spring          ()          >>>          False          # queued=True          motorcar          .          jump          ()          >>>          Truthful        

When a model is removed from the machine, transitions will likewise remove all related events from the queue.

          form          Model          :          def          on_enter_B          (          self          ):          self          .          to_C          ()          # add event to queue ...          self          .          automobile          .          remove_model          (          self          )          # aaaand it's gone        

Provisional transitions

Sometimes yous but desire a item transition to execute if a specific condition occurs. You can do this by passing a method, or listing of methods, in the weather argument:

          # Our Thing grade, now with a bunch of methods that render booleans.          class          Affair          (          object          ):          def          is_flammable          (          self          ):          render          Faux          def          is_really_hot          (          self          ):          return          True          motorcar          .          add_transition          (          'oestrus'          ,          'solid'          ,          'gas'          ,          conditions          =          'is_flammable'          )          machine          .          add_transition          (          'heat'          ,          'solid'          ,          'liquid'          ,          conditions          =          [          'is_really_hot'          ])        

In the to a higher place example, calling oestrus() when the model is in country 'solid' will transition to state 'gas' if is_flammable returns True. Otherwise, it volition transition to state 'liquid' if is_really_hot returns True.

For convenience, there's also an 'unless' argument that behaves exactly similar atmospheric condition, just inverted:

          machine          .          add_transition          (          'heat'          ,          'solid'          ,          'gas'          ,          unless          =          [          'is_flammable'          ,          'is_really_hot'          ])        

In this case, the model would transition from solid to gas whenever heat() fires, provided that both is_flammable() and is_really_hot() render Simulated.

Note that status-checking methods will passively receive optional arguments and/or information objects passed to triggering methods. For instance, the following call:

          lump          .          oestrus          (          temp          =          74          )          # equivalent to lump.trigger('heat', temp=74)        

... would pass the temp=74 optional kwarg to the is_flammable() bank check (possibly wrapped in an EventData instance). For more than on this, run into the Passing data department below.

Callbacks

Y'all tin can attach callbacks to transitions as well as states. Every transition has 'before' and 'after' attributes that comprise a listing of methods to call before and after the transition executes:

          class          Matter          (          object          ):          def          make_hissing_noises          (          cocky          ):          print          (          "HISSSSSSSSSSSSSSSS"          )          def          disappear          (          self          ):          print          (          "where'd all the liquid get?"          )          transitions          =          [          {          'trigger'          :          'cook'          ,          'source'          :          'solid'          ,          'dest'          :          'liquid'          ,          'before'          :          'make_hissing_noises'          },          {          'trigger'          :          'evaporate'          ,          'source'          :          'liquid'          ,          'dest'          :          'gas'          ,          'subsequently'          :          'disappear'          }          ]          lump          =          Matter          ()          machine          =          Machine          (          lump          ,          states          ,          transitions          =          transitions          ,          initial          =          'solid'          )          lump          .          cook          ()          >>>          "HISSSSSSSSSSSSSSSS"          lump          .          evaporate          ()          >>>          "where'd all the liquid go?"        

In that location is also a 'fix' callback that is executed as presently as a transition starts, before whatsoever 'conditions' are checked or other callbacks are executed.

          form          Matter          (          object          ):          rut          =          Faux          attempts          =          0          def          count_attempts          (          self          ):          self          .          attempts          +=          one          def          heat_up          (          self          ):          cocky          .          oestrus          =          random          .          random          ()          <          0.25          def          stats          (          self          ):          impress          (          'It took y'all                    %i                      attempts to melt the lump!'          %          self          .          attempts          )          @property          def          is_really_hot          (          self          ):          return          self          .          heat          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ]          transitions          =          [          {          'trigger'          :          'melt'          ,          'source'          :          'solid'          ,          'dest'          :          'liquid'          ,          'prepare'          :          [          'heat_up'          ,          'count_attempts'          ],          'weather'          :          'is_really_hot'          ,          'after'          :          'stats'          },          ]          lump          =          Affair          ()          auto          =          Machine          (          lump          ,          states          ,          transitions          =          transitions          ,          initial          =          'solid'          )          lump          .          melt          ()          lump          .          melt          ()          lump          .          melt          ()          lump          .          melt          ()          >>>          "It took you 4 attempts to melt the lump!"        

Note that prepare volition not be called unless the current land is a valid source for the named transition.

Default actions meant to be executed earlier or later every transition can be passed to Machine during initialization with before_state_change and after_state_change respectively:

          class          Matter          (          object          ):          def          make_hissing_noises          (          self          ):          print          (          "HISSSSSSSSSSSSSSSS"          )          def          disappear          (          self          ):          print          (          "where'd all the liquid become?"          )          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ]          lump          =          Affair          ()          m          =          Car          (          lump          ,          states          ,          before_state_change          =          'make_hissing_noises'          ,          after_state_change          =          'disappear'          )          lump          .          to_gas          ()          >>>          "HISSSSSSSSSSSSSSSS"          >>>          "where'd all the liquid go?"        

There are likewise two keywords for callbacks which should be executed independently a) of how many transitions are possible, b) if whatsoever transition succeeds and c) even if an fault is raised during the execution of some other callback. Callbacks passed to Machine with prepare_event volition be executed once before processing possible transitions (and their individual prepare callbacks) takes place. Callbacks of finalize_event will be executed regardless of the success of the candy transitions. Note that if an mistake occurred it will be fastened to event_data as error and tin can be retrieved with send_event=True.

          from          transitions          import          Machine          class          Matter          (          object          ):          def          raise_error          (          self          ,          upshot          ):          raise          ValueError          (          "Oh no"          )          def          prepare          (          self          ,          event          ):          print          (          "I am ready!"          )          def          finalize          (          self          ,          consequence          ):          print          (          "Result: "          ,          type          (          outcome          .          mistake          ),          event          .          mistake          )          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ]          lump          =          Matter          ()          g          =          Machine          (          lump          ,          states          ,          prepare_event          =          'prepare'          ,          before_state_change          =          'raise_error'          ,          finalize_event          =          'finalize'          ,          send_event          =          Truthful          )          endeavour          :          lump          .          to_gas          ()          except          ValueError          :          pass          impress          (          lump          .          state          )          # >>> I am ready!          # >>> Result:  <class 'ValueError'> Oh no          # >>> initial        

Sometimes things just don't work out as intended and nosotros need to handle exceptions and make clean upwards the mess to go on things going. We can pass callbacks to on_exception to practice this:

          from          transitions          import          Machine          grade          Affair          (          object          ):          def          raise_error          (          self          ,          result          ):          raise          ValueError          (          "Oh no"          )          def          handle_error          (          self          ,          result          ):          print          (          "Fixing things ..."          )          del          event          .          mistake          # it did non happen if nosotros cannot see it ...          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ,          'plasma'          ]          lump          =          Affair          ()          k          =          Machine          (          lump          ,          states          ,          before_state_change          =          'raise_error'          ,          on_exception          =          'handle_error'          ,          send_event          =          True          )          try          :          lump          .          to_gas          ()          except          ValueError          :          laissez passer          print          (          lump          .          state          )          # >>> Fixing things ...          # >>> initial        

Callable resolution

As you have probably already realized, the standard mode of passing callables to states, conditions and transitions is by proper noun. When processing callbacks and conditions, transitions will utilise their name to think the related callable from the model. If the method cannot be retrieved and it contains dots, transitions will treat the proper noun as a path to a module office and try to import it. Alternatively, yous can pass names of properties or attributes. They will be wrapped into functions only cannot receive event data for obvious reasons. You tin also pass callables such as (bound) functions directly. As mentioned before, yous tin as well pass lists/tuples of callables names to the callback parameters. Callbacks volition be executed in the order they were added.

          from          transitions          import          Machine          from          modern          import          imported_func          import          random          class          Model          (          object          ):          def          a_callback          (          self          ):          imported_func          ()          @property          def          a_property          (          cocky          ):          """ Basically a coin toss. """          return          random          .          random          ()          <          0.v          an_attribute          =          Fake          model          =          Model          ()          car          =          Machine          (          model          =          model          ,          states          =          [          'A'          ],          initial          =          'A'          )          car          .          add_transition          (          'by_name'          ,          'A'          ,          'A'          ,          conditions          =          'a_property'          ,          after          =          'a_callback'          )          machine          .          add_transition          (          'by_reference'          ,          'A'          ,          'A'          ,          unless          =          [          'a_property'          ,          'an_attribute'          ],          after          =          model          .          a_callback          )          machine          .          add_transition          (          'imported'          ,          'A'          ,          'A'          ,          later          =          'mod.imported_func'          )          model          .          by_name          ()          model          .          by_reference          ()          model          .          imported          ()        

The callable resolution is done in Machine.resolve_callable. This method tin be overridden in case more circuitous callable resolution strategies are required.

Case

          class          CustomMachine          (          Automobile          ):          @staticmethod          def          resolve_callable          (          func          ,          event_data          ):          # dispense arguments here and return func, or super() if no manipulation is done.          super          (          CustomMachine          ,          CustomMachine          )          .          resolve_callable          (          func          ,          event_data          )        

Callback execution order

In summary, there are currently three means to trigger events. You tin can call a model'due south convenience functions similar lump.melt(), execute triggers by name such equally lump.trigger("melt") or dispatch events on multiple models with machine.acceleration("melt") (run across section about multiple models in alternative initialization patterns). Callbacks on transitions are and then executed in the following order:

Callback Current State Comments
'machine.prepare_event' source executed once before individual transitions are processed
'transition.prepare' source executed equally shortly as the transition starts
'transition.conditions' source weather may fail and halt the transition
'transition.unless' source conditions may fail and halt the transition
'machine.before_state_change' source default callbacks alleged on model
'transition.earlier' source
'state.on_exit' source callbacks declared on the source state
<Country CHANGE>
'state.on_enter' destination callbacks declared on the destination land
'transition.after' destination
'machine.after_state_change' destination default callbacks alleged on model
'motorcar.on_exception' source/destination callbacks will be executed when an exception has been raised
'machine.finalize_event' source/destination callbacks will be executed even if no transition took identify or an exception has been raised

If any callback raises an exception, the processing of callbacks is not continued. This ways that when an error occurs earlier the transition (in land.on_exit or before), information technology is halted. In instance there is a raise after the transition has been conducted (in state.on_enter or later), the land modify persists and no rollback is happening. Callbacks specified in motorcar.finalize_event will always be executed unless the exception is raised by a finalizing callback itself. Notation that each callback sequence has to be finished before the side by side stage is executed. Blocking callbacks volition halt the execution gild and therefore block the trigger or dispatch call itself. If you desire callbacks to be executed in parallel, you could have a look at the extensions AsyncMachine for asynchronous processing or LockedMachine for threading.

Passing data

Sometimes you need to laissez passer the callback functions registered at machine initialization some information that reflects the model's electric current state. Transitions allows you to do this in two dissimilar ways.

First (the default), you tin can pass any positional or keyword arguments direct to the trigger methods (created when you call add_transition()):

          class          Matter          (          object          ):          def          __init__          (          self          ):          self          .          set_environment          ()          def          set_environment          (          cocky          ,          temp          =          0          ,          pressure          =          101.325          ):          cocky          .          temp          =          temp          self          .          pressure          =          pressure          def          print_temperature          (          self          ):          print          (          "Electric current temperature is                    %d                      degrees celsius."          %          cocky          .          temp          )          def          print_pressure          (          cocky          ):          print          (          "Current pressure is                    %.2f                      kPa."          %          self          .          pressure          )          lump          =          Thing          ()          automobile          =          Machine          (          lump          ,          [          'solid'          ,          'liquid'          ],          initial          =          'solid'          )          machine          .          add_transition          (          'melt'          ,          'solid'          ,          'liquid'          ,          before          =          'set_environment'          )          lump          .          melt          (          45          )          # positional arg;          # equivalent to lump.trigger('cook', 45)          lump          .          print_temperature          ()          >>>          'Current temperature is 45 degrees celsius.'          automobile          .          set_state          (          'solid'          )          # reset state so we can melt again          lump          .          melt          (          pressure          =          300.23          )          # keyword args also work          lump          .          print_pressure          ()          >>>          'Current pressure is 300.23 kPa.'        

You can pass any number of arguments you lot like to the trigger.

There is one important limitation to this arroyo: every callback function triggered by the land transition must be able to handle all of the arguments. This may crusade problems if the callbacks each expect somewhat different data.

To go effectually this, Transitions supports an alternate method for sending information. If you gear up send_event=Truthful at Auto initialization, all arguments to the triggers volition be wrapped in an EventData instance and passed on to every callback. (The EventData object also maintains internal references to the source state, model, transition, machine, and trigger associated with the outcome, in case you need to access these for anything.)

          class          Matter          (          object          ):          def          __init__          (          cocky          ):          cocky          .          temp          =          0          self          .          force per unit area          =          101.325          # Notation that the sole statement is now the EventData case.          # This object stores positional arguments passed to the trigger method in the          # .args belongings, and stores keywords arguments in the .kwargs dictionary.          def          set_environment          (          self          ,          event          ):          self          .          temp          =          event          .          kwargs          .          get          (          'temp'          ,          0          )          cocky          .          pressure          =          event          .          kwargs          .          become          (          'pressure'          ,          101.325          )          def          print_pressure          (          self          ):          impress          (          "Current pressure level is                    %.2f                      kPa."          %          self          .          force per unit area          )          lump          =          Matter          ()          machine          =          Machine          (          lump          ,          [          'solid'          ,          'liquid'          ],          send_event          =          True          ,          initial          =          'solid'          )          auto          .          add_transition          (          'melt'          ,          'solid'          ,          'liquid'          ,          before          =          'set_environment'          )          lump          .          melt          (          temp          =          45          ,          pressure level          =          1853.68          )          # keyword args          lump          .          print_pressure          ()          >>>          'Current pressure is 1853.68 kPa.'        

Culling initialization patterns

In all of the examples so far, we've attached a new Machine instance to a split model (lump, an instance of class Matter). While this separation keeps things tidy (because you don't have to monkey patch a whole bunch of new methods into the Matter class), it tin as well become abrasive, since it requires yous to keep track of which methods are called on the land auto, and which ones are called on the model that the state machine is leap to (e.thou., lump.on_enter_StateA() vs. car.add_transition()).

Fortunately, Transitions is flexible, and supports ii other initialization patterns.

Kickoff, you can create a standalone land machine that doesn't require some other model at all. But omit the model argument during initialization:

          auto          =          Machine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'solid'          )          automobile          .          melt          ()          car          .          land          >>>          'liquid'        

If you initialize the car this fashion, you tin can and so adhere all triggering events (like evaporate(), sublimate(), etc.) and all callback functions directly to the Machine instance.

This approach has the benefit of consolidating all of the state car functionality in ane place, just can feel a niggling scrap unnatural if you remember country logic should be contained within the model itself rather than in a divide controller.

An alternative (potentially improve) approach is to have the model inherit from the Machine form. Transitions is designed to support inheritance seamlessly. (but be sure to override class Machine's __init__ method!):

          class          Matter          (          Machine          ):          def          say_hello          (          cocky          ):          print          (          "hello, new state!"          )          def          say_goodbye          (          self          ):          print          (          "goodbye, sometime state!"          )          def          __init__          (          self          ):          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ]          Machine          .          __init__          (          self          ,          states          =          states          ,          initial          =          'solid'          )          cocky          .          add_transition          (          'melt'          ,          'solid'          ,          'liquid'          )          lump          =          Affair          ()          lump          .          state          >>>          'solid'          lump          .          melt          ()          lump          .          state          >>>          'liquid'        

Here you become to consolidate all state machine functionality into your existing model, which often feels more natural than sticking all of the functionality we want in a separate standalone Machine instance.

A auto tin can handle multiple models which can exist passed as a listing similar Machine(model=[model1, model2, ...]). In cases where you lot want to add together models also as the machine example itself, y'all can pass the class variable placeholder (string) Motorcar.self_literal during initialization like Machine(model=[Auto.self_literal, model1, ...]). You can as well create a standalone automobile, and register models dynamically via automobile.add_model by passing model=None to the constructor. Furthermore, you lot can utilize machine.dispatch to trigger events on all currently added models. Remember to call machine.remove_model if machine is long-lasting and your models are temporary and should exist garbage collected:

          class          Matter          ():          pass          lump1          =          Thing          ()          lump2          =          Matter          ()          # setting 'model' to None or passing an empty listing will initialize the machine without a model          machine          =          Machine          (          model          =          None          ,          states          =          states          ,          transitions          =          transitions          ,          initial          =          'solid'          )          automobile          .          add_model          (          lump1          )          auto          .          add_model          (          lump2          ,          initial          =          'liquid'          )          lump1          .          state          >>>          'solid'          lump2          .          country          >>>          'liquid'          # custom events equally well as auto transitions tin exist dispatched to all models          machine          .          dispatch          (          "to_plasma"          )          lump1          .          country          >>>          'plasma'          assert          lump1          .          state          ==          lump2          .          state          automobile          .          remove_model          ([          lump1          ,          lump2          ])          del          lump1          # lump1 is garbage nerveless          del          lump2          # lump2 is garbage collected        

If you don't provide an initial state in the state car constructor, transitions will create and add a default state called 'initial'. If yous do not desire a default initial country, y'all tin can pass initial=None. Notwithstanding, in this case you need to pass an initial state every time you lot add a model.

          automobile          =          Machine          (          model          =          None          ,          states          =          states          ,          transitions          =          transitions          ,          initial          =          None          )          automobile          .          add_model          (          Matter          ())          >>>          "MachineError: No initial state configured for machine, must specify when adding model."          motorcar          .          add_model          (          Matter          (),          initial          =          'liquid'          )        

Models with multiple states could adhere multiple machines using unlike model_attribute values. Every bit mentioned in Checking land, this will add custom is/to_<model_attribute>_<state_name> functions:

          lump          =          Thing          ()          matter_machine          =          Car          (          lump          ,          states          =          [          'solid'          ,          'liquid'          ,          'gas'          ],          initial          =          'solid'          )          # add a 2nd machine to the same model merely assign a different land aspect          shipment_machine          =          Automobile          (          lump          ,          states          =          [          'delivered'          ,          'shipping'          ],          initial          =          'delivered'          ,          model_attribute          =          'shipping_state'          )          lump          .          state          >>>          'solid'          lump          .          is_solid          ()          # check the default field          >>>          True          lump          .          shipping_state          >>>          'delivered'          lump          .          is_shipping_state_delivered          ()          # bank check the custom field.          >>>          True          lump          .          to_shipping_state_shipping          ()          >>>          True          lump          .          is_shipping_state_delivered          ()          >>>          False        

Logging

Transitions includes very rudimentary logging capabilities. A number of events – namely, country changes, transition triggers, and conditional checks – are logged equally INFO-level events using the standard Python logging module. This means you lot tin can hands configure logging to standard output in a script:

          # Fix logging; The bones log level volition be DEBUG          import          logging          logging          .          basicConfig          (          level          =          logging          .          DEBUG          )          # Fix transitions' log level to INFO; DEBUG messages will be omitted          logging          .          getLogger          (          'transitions'          )          .          setLevel          (          logging          .          INFO          )          # Business every bit usual          machine          =          Machine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'solid'          )          ...        

(Re-)Storing machine instances

Machines are picklable and can be stored and loaded with pickle. For Python iii.iii and earlier dill is required.

          import          dill          equally          pickle          # just required for Python three.3 and earlier          thou          =          Machine          (          states          =          [          'A'          ,          'B'          ,          'C'          ],          initial          =          'A'          )          thousand          .          to_B          ()          chiliad          .          state          >>>          B          # store the machine          dump          =          pickle          .          dumps          (          thou          )          # load the Machine instance over again          m2          =          pickle          .          loads          (          dump          )          m2          .          state          >>>          B          m2          .          states          .          keys          ()          >>>          [          'A'          ,          'B'          ,          'C'          ]        

Extensions

Even though the cadre of transitions is kept lightweight, at that place are a multifariousness of MixIns to extend its functionality. Currently supported are:

  • Diagrams to visualize the current land of a automobile
  • Hierarchical Land Machines for nesting and reuse
  • Threadsafe Locks for parallel execution
  • Async callbacks for asynchronous execution
  • Custom States for extended state-related behaviour

There are 2 mechanisms to retrieve a state automobile example with the desired features enabled. The first approach makes employ of the convenience factory with the 4 parameters graph, nested, locked or asyncio ready to Truthful if the feature is required:

          from          transitions.extensions          import          MachineFactory          # create a machine with mixins          diagram_cls          =          MachineFactory          .          get_predefined          (          graph          =          Truthful          )          nested_locked_cls          =          MachineFactory          .          get_predefined          (          nested          =          True          ,          locked          =          Truthful          )          async_machine_cls          =          MachineFactory          .          get_predefined          (          asyncio          =          True          )          # create instances from these classes          # instances tin be used like simple machines          machine1          =          diagram_cls          (          model          ,          state          ,          transitions          )          machine2          =          nested_locked_cls          (          model          ,          state          ,          transitions          )        

This approach targets experimental employ since in this case the underlying classes exercise not accept to be known. Notwithstanding, classes can too be directly imported from transitions.extensions. The naming scheme is as follows:

Diagrams Nested Locked Asyncio
Car
GraphMachine
HierarchicalMachine
LockedMachine
HierarchicalGraphMachine
LockedGraphMachine
LockedHierarchicalMachine
LockedHierarchicalGraphMachine
AsyncMachine
AsyncGraphMachine
HierarchicalAsyncMachine
HierarchicalAsyncGraphMachine

To use a characteristic-rich state machine, one could write:

          from          transitions.extensions          import          LockedHierarchicalGraphMachine          as          LHGMachine          motorcar          =          LHGMachine          (          model          ,          states          ,          transitions          )        

Diagrams

Additional Keywords:

  • title (optional): Sets the championship of the generated prototype.
  • show_conditions (default False): Shows weather condition at transition edges
  • show_auto_transitions (default False): Shows motorcar transitions in graph
  • show_state_attributes (default False): Evidence callbacks (enter, exit), tags and timeouts in graph

Transitions tin generate basic state diagrams displaying all valid transitions between states. To use the graphing functionality, you'll demand to have graphviz and/or pygraphviz installed:
To generate graphs with the package graphviz, you need to install Graphviz manually or via a package manager.

          sudo apt-get install graphviz graphviz-dev  # Ubuntu and Debian mash install graphviz  # MacOS conda install graphviz python-graphviz  # (Ana)conda                  

Now you can install the actual Python packages

          pip install graphviz pygraphviz # install graphviz and/or pygraphviz manually... pip install transitions[diagrams]  # ... or install transitions with 'diagrams' extras which currently depends on pygraphviz                  

Currently, GraphMachine volition utilise pygraphviz when available and fall back to graphviz when pygraphviz cannot be institute. This can be overridden by passing use_pygraphviz=False to the constructor. Annotation that this default might alter in the future and pygraphviz support may be dropped. With Model.get_graph() you can go the current graph or the region of interest (roi) and draw it like this:

          # import transitions          from          transitions.extensions          import          GraphMachine          m          =          Model          ()          # without further arguments pygraphviz will be used          machine          =          GraphMachine          (          model          =          m          ,          ...          )          # when you want to use graphviz explicitly          machine          =          GraphMachine          (          model          =          m          ,          use_pygraphviz          =          Faux          ,          ...          )          # in cases where auto transitions should exist visible          machine          =          GraphMachine          (          model          =          one thousand          ,          show_auto_transitions          =          True          ,          ...          )          # draw the whole graph ...          m          .          get_graph          ()          .          depict          (          'my_state_diagram.png'          ,          prog          =          'dot'          )          # ... or just the region of interest          # (previous state, active state and all reachable states)          roi          =          m          .          get_graph          (          show_roi          =          True          )          .          draw          (          'my_state_diagram.png'          ,          prog          =          'dot'          )        

This produces something similar this:

state diagram example

Independent of the backend you use, the draw function besides accepts a file descriptor or a binary stream as the first argument. If you set this parameter to None, the byte stream will be returned:

          import          io          with          open          (          'a_graph.png'          ,          'bw'          )          every bit          f          :          # you need to pass the format when you lot pass objects instead of filenames.          m          .          get_graph          ()          .          draw          (          f          ,          format          =          "png"          ,          prog          =          'dot'          )          # you tin can pass a (binary) stream likewise          b          =          io          .          BytesIO          ()          yard          .          get_graph          ()          .          depict          (          b          ,          format          =          "png"          ,          prog          =          'dot'          )          # or just handle the binary string yourself          result          =          k          .          get_graph          ()          .          draw          (          None          ,          format          =          "png"          ,          prog          =          'dot'          )          assert          event          ==          b          .          getvalue          ()        

References and partials passed equally callbacks will be resolved equally skillful as possible:

          from          transitions.extensions          import          GraphMachine          from          functools          import          partial          grade          Model          :          def          clear_state          (          self          ,          deep          =          False          ,          force          =          False          ):          print          (          "Immigration state ..."          )          return          True          model          =          Model          ()          machine          =          GraphMachine          (          model          =          model          ,          states          =          [          'A'          ,          'B'          ,          'C'          ],          transitions          =          [          {          'trigger'          :          'clear'          ,          'source'          :          'B'          ,          'dest'          :          'A'          ,          'weather'          :          model          .          clear_state          },          {          'trigger'          :          'clear'          ,          'source'          :          'C'          ,          'dest'          :          'A'          ,          'weather condition'          :          partial          (          model          .          clear_state          ,          Faux          ,          force          =          Truthful          )},          ],          initial          =          'A'          ,          show_conditions          =          True          )          model          .          get_graph          ()          .          draw          (          'my_state_diagram.png'          ,          prog          =          'dot'          )        

This should produce something similar to this:

state diagram references_example

If the format of references does not arrange your needs, you can override the static method GraphMachine.format_references. If y'all want to skip reference entirely, simply let GraphMachine.format_references return None. Likewise, accept a look at our example IPython/Jupyter notebooks for a more detailed example about how to utilise and edit graphs.

Hierarchical State Machine (HSM)

Transitions includes an extension module which allows nesting states. This allows us to create contexts and to model cases where states are related to certain subtasks in the state car. To create a nested land, either import NestedState from transitions or use a dictionary with the initialization arguments name and children. Optionally, initial can be used to ascertain a sub state to transit to, when the nested land is entered.

          from          transitions.extensions          import          HierarchicalMachine          states          =          [          'standing'          ,          'walking'          ,          {          'proper name'          :          'caffeinated'          ,          'children'          :[          'dithering'          ,          'running'          ]}]          transitions          =          [          [          'walk'          ,          'standing'          ,          'walking'          ],          [          'stop'          ,          'walking'          ,          'standing'          ],          [          'drink'          ,          '*'          ,          'caffeinated'          ],          [          'walk'          ,          [          'caffeinated'          ,          'caffeinated_dithering'          ],          'caffeinated_running'          ],          [          'relax'          ,          'caffeinated'          ,          'standing'          ]          ]          machine          =          HierarchicalMachine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'standing'          ,          ignore_invalid_triggers          =          True          )          car          .          walk          ()          # Walking now          automobile          .          finish          ()          # let's end for a moment          automobile          .          drink          ()          # java time          automobile          .          country          >>>          'caffeinated'          machine          .          walk          ()          # we have to get faster          machine          .          state          >>>          'caffeinated_running'          machine          .          stop          ()          # can't stop moving!          car          .          state          >>>          'caffeinated_running'          car          .          relax          ()          # go out nested state          machine          .          state          # phew, what a ride          >>>          'standing'          # car.on_enter_caffeinated_running('callback_method')        

A configuration making utilise of initial could expect like this:

          # ...          states          =          [          'standing'          ,          'walking'          ,          {          'proper noun'          :          'caffeinated'          ,          'initial'          :          'dithering'          ,          'children'          :          [          'dithering'          ,          'running'          ]}]          transitions          =          [          [          'walk'          ,          'standing'          ,          'walking'          ],          [          'stop'          ,          'walking'          ,          'standing'          ],          # this transition will end in 'caffeinated_dithering'...          [          'drink'          ,          '*'          ,          'caffeinated'          ],          # ... that is why we do non demand practise specify 'caffeinated' here anymore          [          'walk'          ,          'caffeinated_dithering'          ,          'caffeinated_running'          ],          [          'relax'          ,          'caffeinated'          ,          'standing'          ]          ]          # ...        

The initial keyword of the HierarchicalMachine constructor accepts nested states (e.g. initial='caffeinated_running') and a list of states which is considered to be a parallel state (e.g. initial=['A', 'B']) or the current country of another model (initial=model.state) which should be effectively i of the previous mentioned options. Note that when passing a string, transition will cheque the targeted state for initial substates and use this as an entry land. This will be done recursively until a substate does non mention an initial country. Parallel states or a state passed equally a listing will be used 'equally is' and no farther initial evaluation will be conducted.

Notation that your previously created country object must be a NestedState or a derived class of it. The standard State form used in uncomplicated Machine instances lacks features required for nesting.

          from          transitions.extensions.nesting          import          HierarchicalMachine          ,          NestedState          from          transitions          import          State          yard          =          HierarchicalMachine          (          states          =          [          'A'          ],          initial          =          'initial'          )          chiliad          .          add_state          (          'B'          )          # fine          thousand          .          add_state          ({          'name'          :          'C'          })          # likewise fine          k          .          add_state          (          NestedState          (          'D'          ))          # fine as well          m          .          add_state          (          Country          (          'E'          ))          # does not work!        

Some things that have to be considered when working with nested states: Country names are concatenated with NestedState.separator. Currently the separator is gear up to underscore ('_') and therefore behaves similar to the basic motorcar. This means a substate bar from state foo will be known past foo_bar. A substate baz of bar will exist referred to equally foo_bar_baz and then on. When entering a substate, enter volition exist called for all parent states. The same is truthful for exiting substates. Third, nested states can overwrite transition behaviour of their parents. If a transition is not known to the current state information technology will be delegated to its parent.

This means that in the standard configuration, land names in HSMs MUST Non incorporate underscores. For transitions it'southward incommunicable to tell whether machine.add_state('state_name') should add a state named state_name or add together a substate name to the land state. In some cases this is not sufficient however. For example if state names consist of more i word and you want/need to apply underscore to divide them instead of CamelCase. To deal with this, you can alter the grapheme used for separation quite easily. You lot tin can even use fancy unicode characters if you lot use Python 3. Setting the separator to something else than underscore changes some of the behaviour (auto_transition and setting callbacks) though:

          from          transitions.extensions          import          HierarchicalMachine          from          transitions.extensions.nesting          import          NestedState          NestedState          .          separator          =          '↦'          states          =          [          'A'          ,          'B'          ,          {          'name'          :          'C'          ,          'children'          :[          '1'          ,          '2'          ,          {          'name'          :          '3'          ,          'children'          :          [          'a'          ,          'b'          ,          'c'          ]}          ]}          ]          transitions          =          [          [          'reset'          ,          'C'          ,          'A'          ],          [          'reset'          ,          'C↦ii'          ,          'C'          ]          # overwriting parent reset          ]          # we rely on auto transitions          machine          =          HierarchicalMachine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'A'          )          automobile          .          to_B          ()          # exit country A, enter country B          machine          .          to_C          ()          # go out B, enter C          machine          .          to_C          .          s3          .          a          ()          # enter C↦a; enter C↦3↦a;          car          .          state          >>>          'C↦3↦a'          assert          auto          .          is_C          .          s3          .          a          ()          machine          .          to          (          'C↦2'          )          # non interactive; exit C↦iii↦a, exit C↦3, enter C↦2          automobile          .          reset          ()          # exit C↦2; reset C has been overwritten by C↦3          motorcar          .          state          >>>          'C'          machine          .          reset          ()          # exit C, enter A          auto          .          country          >>>          'A'          # southward.on_enter('C↦3↦a', 'callback_method')        

Instead of to_C_3_a() auto transition is called equally to_C.s3.a(). If your substate starts with a digit, transitions adds a prefix 's' ('3' becomes 's3') to the motorcar transition FunctionWrapper to comply with the attribute naming scheme of Python. If interactive completion is not required, to('C↦3↦a') tin be called directly. Additionally, on_enter/exit_<<land proper noun>> is replaced with on_enter/exit(state_name, callback). Land checks can exist conducted in a like way. Instead of is_C_3_a(), the FunctionWrapper variant is_C.s3.a() can be used.

To check whether the electric current land is a substate of a specific land, is_state supports the keyword allow_substates:

          motorcar          .          state          >>>          'C.2.a'          machine          .          is_C          ()          # checks for specific states          >>>          Imitation          car          .          is_C          (          allow_substates          =          True          )          >>>          True          assert          machine          .          is_C          .          s2          ()          is          False          assert          machine          .          is_C          .          s2          (          allow_substates          =          True          )          # FunctionWrapper support allow_substate also        

new in 0.viii.0
You lot tin can employ enumerations in HSMs equally well but keep in heed that Enum are compared past value. If you have a value more than than once in a state tree those states cannot be distinguished.

          states          =          [          States          .          RED          ,          States          .          Yellow          ,          {          'name'          :          States          .          GREEN          ,          'children'          :          [          'tick'          ,          'tock'          ]}]          states          =          [          'A'          ,          {          'name'          :          'B'          ,          'children'          :          states          ,          'initial'          :          States          .          GREEN          },          States          .          Greenish          ]          machine          =          HierarchicalMachine          (          states          =          states          )          machine          .          to_B          ()          car          .          is_GREEN          ()          # returns True even though the actual state is B_GREEN        

new in 0.8.0
HierarchicalMachine has been rewritten from scratch to support parallel states and better isolation of nested states. This involves some tweaks based on community feedback. To get an idea of processing order and configuration take a await at the post-obit example:

          from          transitions.extensions.nesting          import          HierarchicalMachine          import          logging          states          =          [          'A'          ,          'B'          ,          {          'proper noun'          :          'C'          ,          'parallel'          :          [{          'name'          :          '1'          ,          'children'          :          [          'a'          ,          'b'          ,          'c'          ],          'initial'          :          'a'          ,          'transitions'          :          [[          'get'          ,          'a'          ,          'b'          ]]},          {          'name'          :          '2'          ,          'children'          :          [          'ten'          ,          'y'          ,          'z'          ],          'initial'          :          'z'          }],          'transitions'          :          [[          'go'          ,          '2_z'          ,          '2_x'          ]]}]          transitions          =          [[          'reset'          ,          'C_1_b'          ,          'B'          ]]          logging          .          basicConfig          (          level          =          logging          .          INFO          )          machine          =          HierarchicalMachine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'A'          )          machine          .          to_C          ()          # INFO:transitions.extensions.nesting:Exited state A          # INFO:transitions.extensions.nesting:Entered land C          # INFO:transitions.extensions.nesting:Entered land C_1          # INFO:transitions.extensions.nesting:Entered state C_2          # INFO:transitions.extensions.nesting:Entered state C_1_a          # INFO:transitions.extensions.nesting:Entered state C_2_z          auto          .          go          ()          # INFO:transitions.extensions.nesting:Exited country C_1_a          # INFO:transitions.extensions.nesting:Entered country C_1_b          # INFO:transitions.extensions.nesting:Exited state C_2_z          # INFO:transitions.extensions.nesting:Entered state C_2_x          motorcar          .          reset          ()          # INFO:transitions.extensions.nesting:Exited state C_1_b          # INFO:transitions.extensions.nesting:Exited state C_2_x          # INFO:transitions.extensions.nesting:Exited state C_1          # INFO:transitions.extensions.nesting:Exited state C_2          # INFO:transitions.extensions.nesting:Exited state C          # INFO:transitions.extensions.nesting:Entered country B        

When using parallel instead of children, transitions will enter all states of the passed list at the same time. Which substate to enter is defined by initial which should ever point to a direct substate. A novel feature is to ascertain local transitions by passing the transitions keyword in a state definition. The above defined transition ['go', 'a', 'b'] is only valid in C_1. While you can reference substates equally done in ['go', '2_z', '2_x'] you lot cannot reference parent states straight in locally divers transitions. When a parent state is exited, its children will as well be exited. In addition to the processing lodge of transitions known from Auto where transitions are considered in the order they were added, HierarchicalMachine considers hierarchy likewise. Transitions defined in substates will be evaluated first (east.1000. C_1_a is left earlier C_2_z) and transitions divers with wildcard * will (for now) only add transitions to root states (in this example A, B, C) Starting with 0.eight.0 nested states can be added directly and will issue the creation of parent states on-the-fly:

          k          =          HierarchicalMachine          (          states          =          [          'A'          ],          initial          =          'A'          )          m          .          add_state          (          'B_1_a'          )          m          .          to_B_1          ()          assert          1000          .          is_B          (          allow_substates          =          Truthful          )        

Reuse of previously created HSMs

Besides semantic order, nested states are very handy if you want to specify country machines for specific tasks and plan to reuse them. Before 0.8.0, a HierarchicalMachine would non integrate the machine example itself but the states and transitions past creating copies of them. Yet, since 0.8.0 (Nested)Land instances are just referenced which means changes in one machine's collection of states and events will influence the other machine example. Models and their state will non be shared though. Note that events and transitions are besides copied by reference and will be shared by both instances if you practise not use the remap keyword. This modify was done to exist more in line with Car which besides uses passed State instances by reference.

          count_states          =          [          '1'          ,          '2'          ,          '3'          ,          'done'          ]          count_trans          =          [          [          'increase'          ,          '1'          ,          '2'          ],          [          'increase'          ,          '2'          ,          '3'          ],          [          'decrease'          ,          'iii'          ,          '2'          ],          [          'subtract'          ,          'two'          ,          '1'          ],          [          'done'          ,          '3'          ,          'done'          ],          [          'reset'          ,          '*'          ,          '1'          ]          ]          counter          =          HierarchicalMachine          (          states          =          count_states          ,          transitions          =          count_trans          ,          initial          =          '1'          )          counter          .          increase          ()          # love my counter          states          =          [          'waiting'          ,          'collecting'          ,          {          'name'          :          'counting'          ,          'children'          :          counter          }]          transitions          =          [          [          'collect'          ,          '*'          ,          'collecting'          ],          [          'wait'          ,          '*'          ,          'waiting'          ],          [          'count'          ,          'collecting'          ,          'counting'          ]          ]          collector          =          HierarchicalMachine          (          states          =          states          ,          transitions          =          transitions          ,          initial          =          'waiting'          )          collector          .          collect          ()          # collecting          collector          .          count          ()          # let'south come across what we got; counting_1          collector          .          increase          ()          # counting_2          collector          .          increase          ()          # counting_3          collector          .          done          ()          # collector.state == counting_done          collector          .          wait          ()          # collector.state == waiting        

If a HierarchicalMachine is passed with the children keyword, the initial state of this machine volition be assigned to the new parent state. In the in a higher place instance nosotros come across that entering counting volition besides enter counting_1. If this is undesired behaviour and the machine should rather halt in the parent state, the user can pass initial as False similar {'proper name': 'counting', 'children': counter, 'initial': Fake}.

Sometimes you want such an embedded state collection to 'render' which means after it is washed it should exit and transit to ane of your super states. To accomplish this behaviour you tin remap state transitions. In the example to a higher place we would like the counter to return if the state done was reached. This is done as follows:

          states          =          [          'waiting'          ,          'collecting'          ,          {          'proper name'          :          'counting'          ,          'children'          :          counter          ,          'remap'          :          {          'washed'          :          'waiting'          }}]          ...          # same equally above          collector          .          increase          ()          # counting_3          collector          .          done          ()          collector          .          country          >>>          'waiting'          # be enlightened that 'counting_done' volition be removed from the state machine        

As mentioned in a higher place, using remap will copy events and transitions since they could non exist valid in the original state machine. If a reused country machine does non have a final state, you can of grade add the transitions manually. If 'counter' had no 'washed' state, we could simply add ['done', 'counter_3', 'waiting'] to achieve the aforementioned behaviour.

In cases where you want states and transitions to be copied by value rather than reference (for instance, if you want to go on the pre-0.eight behaviour) yous tin do so by creating a NestedState and assigning deep copies of the machine's events and states to information technology.

          from          transitions.extensions.nesting          import          NestedState          from          copy          import          deepcopy          # ... configuring and creating counter                    counting_state          =          NestedState          (          name          =          "counting"          ,          initial          =          'ane'          )          counting_state          .          states          =          deepcopy          (          counter          .          states          )          counting_state          .          events          =          deepcopy          (          counter          .          events          )          states          =          [          'waiting'          ,          'collecting'          ,          counting_state          ]        

For complex state machines, sharing configurations rather than instantiated machines might be more feasible. Especially since instantiated machines must be derived from HierarchicalMachine. Such configurations can be stored and loaded easily via JSON or YAML (see the FAQ). HierarchicalMachine allows defining substates either with the keyword children or states. If both are present, just children volition exist considered.

          counter_conf          =          {          'name'          :          'counting'          ,          'states'          :          [          '1'          ,          'ii'          ,          '3'          ,          'washed'          ],          'transitions'          :          [          [          'increment'          ,          'i'          ,          'two'          ],          [          'increase'          ,          '2'          ,          '3'          ],          [          'subtract'          ,          'iii'          ,          '2'          ],          [          'decrease'          ,          'ii'          ,          'ane'          ],          [          'done'          ,          '3'          ,          'done'          ],          [          'reset'          ,          '*'          ,          '1'          ]          ],          'initial'          :          'i'          }          collector_conf          =          {          'name'          :          'collector'          ,          'states'          :          [          'waiting'          ,          'collecting'          ,          counter_conf          ],          'transitions'          :          [          [          'collect'          ,          '*'          ,          'collecting'          ],          [          'look'          ,          '*'          ,          'waiting'          ],          [          'count'          ,          'collecting'          ,          'counting'          ]          ],          'initial'          :          'waiting'          }          collector          =          HierarchicalMachine          (          **          collector_conf          )          collector          .          collect          ()          collector          .          count          ()          collector          .          increase          ()          assert          collector          .          is_counting_2          ()        

Threadsafe(-ish) State Machine

In cases where event dispatching is done in threads, i tin can utilize either LockedMachine or LockedHierarchicalMachine where function access (!sic) is secured with reentrant locks. This does non relieve you from corrupting your machine by tinkering with member variables of your model or country machine.

          from          transitions.extensions          import          LockedMachine          from          threading          import          Thread          import          time          states          =          [          'A'          ,          'B'          ,          'C'          ]          machine          =          LockedMachine          (          states          =          states          ,          initial          =          'A'          )          # let us assume that inbound B volition have some time          thread          =          Thread          (          target          =          car          .          to_B          )          thread          .          showtime          ()          time          .          sleep          (          0.01          )          # thread requires some time to start          machine          .          to_C          ()          # synchronized access; won't execute before thread is done          # accessing attributes direct          thread          =          Thread          (          target          =          motorcar          .          to_B          )          thread          .          start          ()          motorcar          .          new_attrib          =          42          # not synchronized! will mess with execution society        

Any python context director can be passed in via the machine_context keyword argument:

          from          transitions.extensions          import          LockedMachine          from          threading          import          RLock          states          =          [          'A'          ,          'B'          ,          'C'          ]          lock1          =          RLock          ()          lock2          =          RLock          ()          machine          =          LockedMachine          (          states          =          states          ,          initial          =          'A'          ,          machine_context          =          [          lock1          ,          lock2          ])        

Any contexts via machine_model will be shared betwixt all models registered with the Machine. Per-model contexts can be added equally well:

          lock3          =          RLock          ()          machine          .          add_model          (          model          ,          model_context          =          lock3          )        

It's important that all user-provided context managers are re-entrant since the state motorcar volition call them multiple times, even in the context of a single trigger invocation.

Using async callbacks

If you are using Python three.7 or later on, you tin can use AsyncMachine to piece of work with asynchronous callbacks. You lot tin mix synchronous and asynchronous callbacks if you similar just this may take undesired side effects. Annotation that events need to be awaited and the consequence loop must also be handled by you lot.

          from          transitions.extensions.asyncio          import          AsyncMachine          import          asyncio          import          time          course          AsyncModel          :          def          prepare_model          (          self          ):          print          (          "I am synchronous."          )          cocky          .          start_time          =          time          .          time          ()          async          def          before_change          (          cocky          ):          impress          (          "I am asynchronous and volition block now for 100 milliseconds."          )          expect          asyncio          .          sleep          (          0.1          )          print          (          "I am done waiting."          )          def          sync_before_change          (          cocky          ):          print          (          "I am synchronous and will block the effect loop (what I probably shouldn't)"          )          fourth dimension          .          sleep          (          0.1          )          print          (          "I am done waiting synchronously."          )          def          after_change          (          self          ):          impress          (          f          "I am synchronous again. Execution took                    {          int          ((          time          .          time          ()          -          cocky          .          start_time          )          *          1000          )          }                      ms."          )          transition          =          dict          (          trigger          =          "start"          ,          source          =          "Start"          ,          dest          =          "Washed"          ,          prepare          =          "prepare_model"          ,          earlier          =          [          "before_change"          ]          *          5          +          [          "sync_before_change"          ],          after          =          "after_change"          )          # execute before function in asynchronously v times          model          =          AsyncModel          ()          machine          =          AsyncMachine          (          model          ,          states          =          [          "Starting time"          ,          "Washed"          ],          transitions          =          [          transition          ],          initial          =          'Get-go'          )          asyncio          .          get_event_loop          ()          .          run_until_complete          (          model          .          start          ())          # >>> I am synchronous.          #     I am asynchronous and will cake now for 100 milliseconds.          #     I am asynchronous and will block now for 100 milliseconds.          #     I am asynchronous and volition block now for 100 milliseconds.          #     I am asynchronous and will block now for 100 milliseconds.          #     I am asynchronous and volition block now for 100 milliseconds.          #     I am synchronous and will cake the event loop (what I probably shouldn't)          #     I am washed waiting synchronously.          #     I am washed waiting.          #     I am done waiting.          #     I am washed waiting.          #     I am washed waiting.          #     I am done waiting.          #     I am synchronous again. Execution took 101 ms.          assert          model          .          is_Done          ()        

Then, why do you need to use Python 3.vii or later you lot may ask. Async support has been introduced earlier. AsyncMachine makes utilize of contextvars to handle running callbacks when new events arrive before a transition has been finished:

          async          def          await_never_return          ():          await          asyncio          .          sleep          (          100          )          raise          ValueError          (          "That took too long!"          )          async          def          fix          ():          await          m2          .          ready          ()          m1          =          AsyncMachine          (          states          =          [          'A'          ,          'B'          ,          'C'          ],          initial          =          'A'          ,          proper noun          =          "m1"          )          m2          =          AsyncMachine          (          states          =          [          'A'          ,          'B'          ,          'C'          ],          initial          =          'A'          ,          name          =          "m2"          )          m2          .          add_transition          (          trigger          =          'become'          ,          source          =          'A'          ,          dest          =          'B'          ,          before          =          await_never_return          )          m2          .          add_transition          (          trigger          =          'gear up'          ,          source          =          'A'          ,          dest          =          'C'          )          m1          .          add_transition          (          trigger          =          'go'          ,          source          =          'A'          ,          dest          =          'B'          ,          after          =          'go'          )          m1          .          add_transition          (          trigger          =          'go'          ,          source          =          'B'          ,          dest          =          'C'          ,          subsequently          =          fix          )          asyncio          .          get_event_loop          ()          .          run_until_complete          (          asyncio          .          assemble          (          m2          .          get          (),          m1          .          get          ()))          affirm          m1          .          state          ==          m2          .          state        

This example actually illustrates two things: Kickoff, that 'get' called in m1's transition from A to be B is not cancelled and second, calling m2.fix() will halt the transition try of m2 from A to B by executing 'prepare' from A to C. This separation would not be possible without contextvars. Note that prepare and weather are NOT treated equally ongoing transitions. This ways that after weather have been evaluated, a transition is executed fifty-fifty though another issue already happened. Tasks will simply be cancelled when run as a before callback or later on.

AsyncMachine features a model-special queue mode which can exist used when queued='model' is passed to the constructor. With a model-specific queue, events will only be queued when they belong to the aforementioned model. Furthermore, a raised exception will only articulate the event queue of the model that raised that exception. For the sake of simplicity, permit's presume that every result in asyncion.gather beneath is not triggered at the aforementioned time but slightly delayed:

          asyncio          .          gather          (          model1          .          event1          (),          model1          .          event2          (),          model2          .          event1          ())          # execution social club with AsyncMachine(queued=Truthful)          # model1.event1 -> model1.event2 -> model2.event1          # execution order with AsyncMachine(queued='model')          # (model1.event1, model2.event1) -> model1.event2          asyncio          .          assemble          (          model1          .          event1          (),          model1          .          mistake          (),          model1          .          event3          (),          model2          .          event1          (),          model2          .          event2          (),          model2          .          event3          ())          # execution order with AsyncMachine(queued=True)          # model1.event1 -> model1.error          # execution order with AsyncMachine(queued='model')          # (model1.event1, model2.event1) -> (model1.fault, model2.event2) -> model2.event3        

Note that queue modes must non exist changed after car construction.

Calculation features to states

If your superheroes demand some custom behaviour, yous can throw in some actress functionality by decorating machine states:

          from          time          import          slumber          from          transitions          import          Automobile          from          transitions.extensions.states          import          add_state_features          ,          Tags          ,          Timeout          @add_state_features          (          Tags          ,          Timeout          )          class          CustomStateMachine          (          Machine          ):          pass          grade          SocialSuperhero          (          object          ):          def          __init__          (          self          ):          self          .          entourage          =          0          def          on_enter_waiting          (          self          ):          cocky          .          entourage          +=          1          states          =          [{          'name'          :          'preparing'          ,          'tags'          :          [          'home'          ,          'busy'          ]},          {          'proper name'          :          'waiting'          ,          'timeout'          :          1          ,          'on_timeout'          :          'get'          },          {          'name'          :          'away'          }]          # The city needs us!          transitions          =          [[          'done'          ,          'preparing'          ,          'waiting'          ],          [          'bring together'          ,          'waiting'          ,          'waiting'          ],          # Entering Waiting over again will increase our entourage          [          'go'          ,          'waiting'          ,          'away'          ]]          # Okay, let' motion          hero          =          SocialSuperhero          ()          motorcar          =          CustomStateMachine          (          model          =          hero          ,          states          =          states          ,          transitions          =          transitions          ,          initial          =          'preparing'          )          assert          hero          .          land          ==          'preparing'          # Preparing for the night shift          assert          machine          .          get_state          (          hero          .          state          )          .          is_busy          # We are at home and busy          hero          .          done          ()          assert          hero          .          state          ==          'waiting'          # Waiting for fellow superheroes to join us          assert          hero          .          entourage          ==          1          # It's just u.s.a. so far          sleep          (          0.7          )          # Waiting...          hero          .          join          ()          # Weeh, we got company          sleep          (          0.five          )          # Waiting...          hero          .          join          ()          # Even more company \o/          sleep          (          2          )          # Waiting...          assert          hero          .          country          ==          'away'          # Impatient superhero already left the edifice          assert          machine          .          get_state          (          hero          .          state          )          .          is_home          is          False          # Yupp, not at dwelling anymore          assert          hero          .          entourage          ==          3          # At least he is not alone        

Currently, transitions comes equipped with the following country features:

  • Timeout -- triggers an effect later some fourth dimension has passed

    • keyword: timeout (int, optional) -- if passed, an entered state volition timeout after timeout seconds
    • keyword: on_timeout (string/callable, optional) -- volition be called when timeout fourth dimension has been reached
    • will enhance an AttributeError when timeout is set merely on_timeout is not
    • Note: A timeout is triggered in a thread. This implies several limitations (e.thou. communicable Exceptions raised in timeouts). Consider an consequence queue for more sophisticated applications.
  • Tags -- adds tags to states

    • keyword: tags (listing, optional) -- assigns tags to a state
    • State.is_<tag_name> will return True when the state has been tagged with tag_name, else Fake
  • Mistake -- raises a MachineError when a state cannot be left

    • inherits from Tags (if you employ Error practice non use Tags)
    • keyword: accepted (bool, optional) -- marks a state every bit accustomed
    • alternatively the keyword tags can exist passed, containing 'accepted'
    • Note: Errors will merely be raised if auto_transitions has been set to False. Otherwise every state can be exited with to_<state> methods.
  • Volatile -- initialises an object every time a state is entered

    • keyword: volatile (class, optional) -- every time the land is entered an object of type course volition exist assigned to the model. The attribute name is defined by claw. If omitted, an empty VolatileObject volition exist created instead
    • keyword: hook (string, default='telescopic') -- The model's attribute proper noun for the temporal object.

You can write your own Land extensions and add them the aforementioned way. Just annotation that add_state_features expects Mixins. This ways your extension should always telephone call the overridden methods __init__, enter and exit. Your extension may inherit from State simply volition too work without information technology. Using @add_state_features has a drawback which is that decorated machines cannot be pickled (more precisely, the dynamically generated CustomState cannot be pickled). This might exist a reason to write a defended custom country form instead. Depending on the chosen land car, your custom land class may need to provide certain state features. For instance, HierarchicalMachine requires your custom state to be an instance of NestedState (State is not sufficient). To inject your states y'all tin can either assign them to your Motorcar's course attribute state_cls or override Machine.create_state in case you need some specific procedures done whenever a state is created:

          from          transitions          import          Machine          ,          State          class          MyState          (          State          ):          pass          class          CustomMachine          (          Machine          ):          # Utilize MyState as state class          state_cls          =          MyState          class          VerboseMachine          (          Machine          ):          # `Automobile._create_state` is a class method but we can                    # override it to be an case method          def          _create_state          (          self          ,          *          args          ,          **          kwargs          ):          impress          (          "Creating a new country with auto '          {0}          '"          .          format          (          cocky          .          name          ))          render          MyState          (          *          args          ,          **          kwargs          )        

If you lot desire to avoid threads in your AsyncMachine entirely, you lot tin replace the Timeout state feature with AsyncTimeout from the asyncio extension:

          import          asyncio          from          transitions.extensions.states          import          add_state_features          from          transitions.extensions.asyncio          import          AsyncTimeout          ,          AsyncMachine          @add_state_features          (          AsyncTimeout          )          class          TimeoutMachine          (          AsyncMachine          ):          pass          states          =          [          'A'          ,          {          'proper noun'          :          'B'          ,          'timeout'          :          0.2          ,          'on_timeout'          :          'to_C'          },          'C'          ]          g          =          TimeoutMachine          (          states          =          states          ,          initial          =          'A'          ,          queued          =          True          )          # meet remark below          asyncio          .          run          (          asyncio          .          look          ([          1000          .          to_B          (),          asyncio          .          slumber          (          0.1          )]))          affirm          m          .          is_B          ()          # timeout shouldn't exist triggered          asyncio          .          run          (          asyncio          .          expect          ([          m          .          to_B          (),          asyncio          .          sleep          (          0.three          )]))          affirm          thousand          .          is_C          ()          # now timeout should have been processed        

You should consider passing queued=True to the TimeoutMachine constructor. This will make sure that events are processed sequentially and avoid asynchronous racing weather condition that may appear when timeout and event happen in close proximity.

Using transitions together with Django

Yous can have a look at the FAQ for some inspiration or checkout django-transitions. It has been developed past Christian Ledermann and is also hosted on Github. The documentation contains some usage examples.

I have a [problems written report/consequence/question]...

Start, congratulations! You reached the end of the documentation! If you lot want to try out transitions before y'all install it, you can do that in an interactive Jupyter notebook at mybinder.org. But click this push button 👉 Binder.

For bug reports and other bug, please open up an upshot on GitHub.

For usage questions, mail on Stack Overflow, making sure to tag your question with the pytransitions tag. Do non forget to take a look at the extended examples!

For any other questions, solicitations, or large unrestricted monetary gifts, email Tal Yarkoni (initial author) and/or Alexander Neumann (current maintainer).

Invalid Block Tag On Did You Forget To Register Or Load This Tag?,

Source: https://pypi.org/project/transitions/

Posted by: kramerdosed1999.blogspot.com

0 Response to "Invalid Block Tag On Did You Forget To Register Or Load This Tag?"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel