Adding and triggering a transition within on_enter state callback - pytransitions

I want to store a previous state A when entering state B using a state history. Later 'on_enter' of another state X, add a transition X->A, trigger it and return to state A.
A -> B (store A) -> C -> X (add transition from X to A) -> A
from transitions.extensions import HierarchicalGraphMachine
class Matter(HierarchicalGraphMachine):
def __init__(self, states):
# self.separator = '/'
self.history = []
self.stored = []
HierarchicalGraphMachine.__init__(
self,
states=states,
initial='root',
show_state_attributes=True
)
#property
def state(self):
return self.history[-1]
#state.setter
def state(self, value):
self.history.append(value)
def store_last(self):
self.stored.append(self.history[-2])
def to_stored(self):
stored = self.stored.pop()
temp_transition = {
'trigger': 'jump',
'source': self.state,
'dest': stored
}
self.add_transition(**temp_transition)
self.trigger('jump')
self.remove_transition(**temp_transition)
TRANSITIONS = [
['walk', 'standing', 'walking'],
['stop', 'walking', 'standing'],
['drink', '*', 'caffeinated'],
['walk',
['caffeinated', 'caffeinated_dithering'],
'caffeinated_running'],
['relax', 'caffeinated', 'standing']
]
STATES = [{
'name': 'root',
'initial': 'standing',
'transitions': TRANSITIONS,
'children': [
{
'name': 'standing',
},
{
'name': 'walking',
'on_enter': 'store_last',
},
{
'name': 'caffeinated',
'children': [
'dithering',
{
'name': 'running',
'on_enter': 'to_stored'
}
]
}
]
}]
If I run the code skipping on_enter the transition is added, triggered and removed as expected.
# this works
lump = Matter(states=STATES)
lump.trigger('walk')
assert lump.history == ['root', 'root_standing', 'root_walking']
assert lump.stored == ['root_standing']
lump.trigger('drink')
# set state manually to skip on_enter
lump.state = 'root_caffeinated_running'
# run on_enter method manually works
lump.to_stored()
assert lump.state == 'root_standing'
assert lump.stored == []
assert lump.get_nested_transitions(trigger='jump') == []
lump.get_graph().draw('my_state_diagram.pdf', prog='dot')
If I run it within on_enter I get an error "MachineError: "Can't trigger event 'jump' from state(s) root_caffeinated_running!"
# cannot do the jump using on_enter
lump = Matter(states=STATES)
lump.trigger('walk')
lump.trigger('drink')
lump.trigger('walk')
# Can't trigger event 'jump' from state(s) root_caffeinated_running!
lump.get_graph().draw('my_state_diagram.pdf', prog='dot')

By default transitions will add auto transitions which can be used to reach every state directly without the need to add transitions temporarily. In combination with Machine.trigger that can be used to call events by their name, your to_stored method could be simplified to:
def to_stored(self):
stored = self.stored.pop()
self.trigger(f"to_{stored}")

Related

How do I bind a Celery task to a specific queue?

I want to write a task that is only executable from within a given queue - if somebody tries to pass a different queue into the routing_key parameter of apply_async I want to raise an exception. How do I do this?
You could write your own task that would check to make sure a valid routing key is being passed in when apply_async is being called. You can also apply this to queues. Set up routes and queues in your config:
import celery
from kombu import Queue, Exchange
app = celery.Celery('app')
app.conf.CELERY_QUEUES = (
Queue('add', Exchange('default'), routing_key='good'),
)
app.conf.CELERY_ROUTES = {
'app.add': {
'queue': 'add',
'routing_key': 'good'
}
}
Now, create your own Task class that will perform the check on the routing key. You'll need to override apply_async:
class RouteCheckerTask(celery.Task):
abstract = True
def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
link=None, link_error=None, **options):
app = self._get_app()
routing_key = options.get('routing_key', None)
if routing_key:
valid_routes = [v['routing_key'] for k, v in app.conf.CELERY_ROUTES.items()]
is_valid = routing_key in valid_routes
if not is_valid:
raise NotImplementedError('{} is not a valid routing key. Options are: {}'.format(routing_key, valid_routes))
if app.conf.CELERY_ALWAYS_EAGER:
return self.apply(args, kwargs, task_id=task_id or uuid(), link=link, link_error=link_error, **options)
# add 'self' if this is a "task_method".
if self.__self__ is not None:
args = args if isinstance(args, tuple) else tuple(args or ())
args = (self.__self__, ) + args
return app.send_task(
self.name, args, kwargs, task_id=task_id, producer=producer,
link=link, link_error=link_error, result_cls=self.AsyncResult,
**dict(self._get_exec_options(), **options)
)
Base your tasks from this one and call apply_async normally:
#app.task(base=RouteCheckerTask)
def add(x, y):
return x + y
# Fails
add.apply_async([1, 2], routing_key='bad')
# Passes
add.apply_async([1, 2], routing_key='good')

Why does my Akka FSM event time out?

As a learning exercise for Akka FSM, I modeled a simplified order processing flow at a coffee shop. Attached is the state transition diagram. However, one of the test cases I wrote times out and I don't understand why.
FSM (case classes not shown for brevity):
class OrderSystem extends Actor with ActorLogging with LoggingFSM[State, Data] {
startWith(OrderPending, Data(OrderPending, PaymentPending))
when(OrderPending) {
case Event(BaristaIsBusy, _) => stay
case Event(BaristaIsAvailable(_, PaymentPending), _) => goto(OrderPlaced) using Data(stateName, PaymentPending)
case Event(b: BaristaIsAvailable, _) => goto(OrderReady)
}
val waiting = Data(OrderPlaced, PaymentAccepted)
when(OrderPlaced) {
case Event(b: BaristaIsAvailable, `waiting`) => println("1"); goto(OrderReady)
case Event(b: BaristaIsBusy, `waiting`) => println("2"); goto(OrderPending) using `waiting`
case Event(_, Data(_, PaymentDeclined)) => println("3"); goto(OrderClosed)
case Event(_, Data(_, PaymentPending)) => println("4"); stay
}
when(OrderReady) {
case Event(HappyWithOrder, _) => goto(OrderClosed)
case Event(NotHappyWithOrder, _) => goto(OrderPending) using Data(stateName, PaymentAccepted)
}
when(OrderClosed) {
case _ => stay
}
whenUnhandled {
case Event(e, s) => {
// state name is available as 'stateName'
log.warning("Received unhandled request {} in state {}/{}", e, stateName, s)
stay
}
}
// previous state data is available as 'stateData' and next state data as 'nextStateData'
// not necessary as LoggingFSM (if configured) will take care of logging
onTransition {
case _ -> nextState => log.info("Entering state: {} with payment activity: {} from state: {} with payment activity: {}.",
nextState, stateData.paymentActivity, nextStateData.fromState, nextStateData.paymentActivity)
}
initialize()
}
Failing test:
it should "stay in OrderPlaced state as long as customer has not paid" in {
val orderSystem = system.actorOf(Props[OrderSystem])
orderSystem ! BaristaIsAvailable(OrderPending, PaymentPending)
orderSystem ! SubscribeTransitionCallBack(testActor)
expectMsg(CurrentState(orderSystem, OrderPlaced))
orderSystem ! BaristaIsAvailable(OrderPlaced, PaymentPending)
expectMsg(CurrentState(orderSystem, OrderPlaced))
}
Logs:
2015-09-22 23:29:15.236 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - processing Event(BaristaIsAvailable(OrderPending,PaymentPending),Data(OrderPending,PaymentPending)) from Actor[akka://order-system/system/testActor1#-2143558060]
2015-09-22 23:29:15.238 [order-system-akka.actor.default-dispatcher-2] [INFO ] n.a.s.o.OrderSystem - Entering state: OrderPlaced with payment activity: PaymentPending from state: OrderPending with payment activity: PaymentPending.
2015-09-22 23:29:15.239 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - transition OrderPending -> OrderPlaced
4
2015-09-22 23:29:15.242 [order-system-akka.actor.default-dispatcher-2] [DEBUG] n.a.s.o.OrderSystem - processing Event(BaristaIsAvailable(OrderPlaced,PaymentPending),Data(OrderPending,PaymentPending)) from Actor[akka://order-system/system/testActor1#-2143558060]
[31m- should stay in OrderPlaced state as long as customer has not paid *** FAILED ***[0m
[31m java.lang.AssertionError: assertion failed: timeout (3 seconds)
SubscribeTransitionCallBack will only deliver a CurrentState once, and then only Transition callbacks.
You could try to do this:
it should "stay in OrderPlaced state as long as customer has not paid" in {
val orderSystem = TestFSMRef(new OrderSystem)
orderSystem ! SubscribeTransitionCallBack(testActor)
// fsm first answers with current state
expectMsgPF(1.second. s"OrderPending as current state for $orderSystem") {
case CurrentState('orderSystem', OrderPending) => ok
}
// from now on the subscription will yield 'Transition' messages
orderSystem ! BaristaIsAvailable(OrderPending, PaymentPending)
expectMsgPF(1.second, s"Transition from OrderPending to OrderPlaced for $orderSystem") {
case Transition(`orderSystem`, OrderPending, OrderPlaced) => ok
}
orderSystem ! BaristaIsAvailable(OrderPlaced, PaymentPending)
// there is no transition, so there should not be a callback.
expectNoMsg(1.second)
/*
// alternatively, if your state data changes, using TestFSMRef, you could check state data blocking for some time
awaitCond(
p = orderSystem.stateData == ???,
max = 2.seconds,
interval = 200.millis,
message = "waiting for expected state data..."
)
// awaitCond will throw an exception if the condition is not met within max timeout
*/
success
}

Play Framework: How to read an entire configuration section consisting of unknown keys

Here below is how I'd like to configure security profiles for my Play application – each entry in auth.securityProfiles consists of an Operation => Roles pair:
auth {
securityProfiles {
myOperation1 = "author, auditor"
myOperation2 = "admin"
myOperationN = "auditor, default"
}
}
How do I read all the entries in section auth.securityProfiles to produce a Map like this?
val securityProfiles = Map(
"myOperation1" -> "author, auditor",
"myOperation2" -> "admin",
"myOperationN" -> "auditor, default"
)
Thanks.
Here is my solution... I've just modified the configuration like this...
auth {
securityProfiles = [
{
operation = "myOperation1"
roles = ["author", "auditor"]
}
{
operation = "myOperation2"
roles = ["admin"]
}
{
operation = "myOperationN"
roles = ["auditor", "default"]
}
]
}
... and then read it with the following code snipper:
import scala.collection.mutable.Map
var securityProfiles = Map[String, List[String]]().withDefaultValue(List.empty)
configuration.getConfigList("auth.securityProfiles").map { _.toList.map { config =>
config.getString("operation").map { op =>
securityProfiles += (op -> config.getStringList("roles").map(_.toList).getOrElse(List.empty))
}
}}
I hope that helps.

ReactiveMongo plugin play framework seems to restart with every query

I am trying to write a play! framework 2.1 application with ReactiveMongo, following this sample. however, with every call to the plugin, it seems that the application hangs after the operation completes, than the pluging closes and restarts, and we move on. functionality work, but i am not sure if it's not crashing and restarting along the way.
code:
def db = ReactiveMongoPlugin.db
def nodesCollection = db("nodes")
def index = Action {implicit request =>
Async {
Logger.debug("serving nodes list")
implicit val nodeReader = Node.Node7BSONReader
val query = BSONDocument(
"$query" -> BSONDocument()
)
val found = nodesCollection.find(query)
found.toList.map { nodes =>
Logger.debug("returning nodes list to requester")
Ok(views.html.nodes.nodes(nodes))
}
}
}
def showCreationForm = Action { implicit request =>
Ok(views.html.nodes.editNode(None, Node.nodeCredForm))
}
def create = Action { implicit request =>
Node.nodeCredForm.bindFromRequest.fold(
errors => {
Ok(views.html.nodes.editNode(None, errors))
},
node => AsyncResult {
Node.createNode(node._1, node._2, node._3) match {
case Right(myNode) => {
nodesCollection.insert(myNode).map { _ =>
Redirect(routes.Nodes.index).flashing("success" -> "Node Added")
}
}
case Left(message) => {
Future(Redirect(routes.Nodes.index).flashing("error" -> message))
}
}
}
)
}
logging:
[debug] application - in Node constructor
[debug] application - done inseting, redirecting to nodes page
--- (RELOAD) ---
[info] application - ReactiveMongoPlugin stops, closing connections...
[info] application - ReactiveMongo stopped. [Success(Closed)]
[info] application - ReactiveMongoPlugin starting...
what is wrong with this picture?
There seems nothing wrong with that picture. If you only showed me that log output I would say you would have changed a file in you play application. Which would cause the application to reload.
I guess that is not the case, so your database is probably located within your application directory, causing the application to reload on each change. Where is your database located?

How can a document claim it's persisted, when a count on the class returns 0?

How can a document claim foo.persisted? == true, when foo.class.count == 0?
More importantly, how can I get the document to truly persist?
Update: calling Mongo::Foo.create() from the controller does increment the count. That is expected. Calling create, or new and save, from within a separate module/class does not increment the count.
Update: I have tried changing new/save calls to create
Update: I have also tried #foo_class.with(collection: "mongo_foos").create
[42] pry(#<FoosController>)> foo.class
=> Mongo::Foo
[43] pry(#<FoosController>)> foo.persisted?
=> true
[44] pry(#<FoosController>)> Mongo::Foo.count
=> 0
[47] pry(#<FoosController>)> foo.id
=> "5081a1a786ddc8e035000021"
[48] pry(#<FoosController>)> foo
=> #<Mongo::Foo _id: 5081a1a786ddc8e035000021, _type: nil, foo_daddy_id: nil, old_foo_daddy_id: 219, member_id: "F008AR", unix_time: nil, date_time: 2012-10-19 13:50:54 UTC, submitted: nil, json: nil>
Here's how the document class is defined:
module Mongo
class Foo
include Mongoid::Document
field :foo_daddy_id
field :old_foo_daddy_id, type: Integer
field :member_id, type: String
field :unix_time, type: Integer
field :date_time, type: DateTime
field :submitted, type: Integer
field :json, type: Hash
belongs_to :foo_daddy, class_name: "Mongo::FooDaddy"
embeds_many :foo_dumplings, class_name: "Mongo::FooDumpling"
end
end
The doc is being created with foo.new(params); foo.save:
module FooThingy
module Foo
class Create < ::FooThingy::Base
def initialize(options)
# Sets instance variables used below
end
def execute!
#foo = #foo_class.new({
date_time: DateTime.strptime(#params["submitted_timestamp"], "%m-%d-%Y %H:%M:%S"),
member_id: (#params[:member_id].present? ? #params[:member_id] : nil),
old_foo_daddy_id: #params[:old_foo_daddy_id]
})
embed_foo_dumplings
if #foo.save
return FooThingy::Result.new(success: true, data: { foo: #foo })
else
return FooThingy::Result.new(success: false, data: { foo: #foo })
end
end
private
def embed_foo_dumplings
# Embeds foo_dumplings
end
end
end
end
Here's where I call that module to create the document:
class FoosController < ApplicationController
def create
foo_creator = FooThingy::Foo::Create.new(params: params, foo_class: Mongo::Foo)
foo = foo_creator.execute!
foo = foo.data[:foo]
binding.pry
end
end
This problem, I think, is less interesting than it appears. I've modified my config/mongoid.yml to specify a new database, and everything behaves as expected. Problem was likely due to limitations on 32-bit MongoDB installations.