Pytransitions: AsyncMachine Sequential Resolution of Asynchronous Dependent Callbacks - pytransitions

Note:
This question is related to Python's FSM library pytransitions
I'm looking for a way to resolve method callbacks sequentially when they've been mentioned as a list in prepare or/and before or/and after. I'm using AsyncMachine module from transitions.extensions.asyncio
Expected Result:
1Done_2Done_3Done
Getting:
None_3Done
A sample code to replicate current situation:
import asyncio
from transitions.extensions.asyncio import AsyncMachine
class Model:
STATES = ['A', 'B']
TRANSITIONS = [
{'trigger': 'next', 'source': 'A', 'dest': 'B',
'prepare': ['initialize1', 'initialize2', 'initialize3'], 'before': [], 'after': ['show_attributes']}
]
def __init__(self, name, state='initial'):
self.name = name
self.state = state
self.attribute_1 = None
self.attribute_2 = None
self.attribute_3 = None
async def initialize1(self):
await asyncio.sleep(1) # This is expensive operation and will take some time.
self.attribute_1 = '1Done'
print(f'{self.name} {self.state} -> Initialized1: ', self.attribute_1)
async def initialize2(self):
await asyncio.sleep(0.5) # This is expensive operation and will take some time.
self.attribute_2 = f'{self.attribute_1}_2Done'
print(f'{self.name} {self.state} -> Initialized2: ', self.attribute_2)
async def initialize3(self):
self.attribute_3 = f'{self.attribute_2}_3Done'
print(f'{self.name} {self.state} -> Initialized3: ', self.attribute_3)
async def show_attributes(self):
print(f'{self.name} {self.state} -> Showing all: {self.attribute_3}')
machine = AsyncMachine(
model=None,
states=Model.STATES,
transitions=Model.TRANSITIONS,
initial=None,
queued='model'
# queued=True
)
async def main():
model1 = Model(name='Model1', state='A')
machine.add_model(model1, initial=model1.state)
await machine.dispatch('next')
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
As it's been shown in the code 'prepare': ['initialize1', 'initialize2', 'initialize3'] I'm looking for a way to call initialize2 once initialize1 is resolved and initialize3 once both initialize1 and initialize2 methods are resolved. Currently, they've been called parallelly which is a good feature but it would be awesome if there is a way to make them execute/resolve in sequence.
Of course, I can add one more method like initialize_all and then call all the above methods inside it. But think about how many new methods I've to keep adding to deal with real-world problems. I want to make my functions reusable and smaller just for a specific task.

I went through pytransitions source code and I found two ways to achieve the feature I was looking for.
I think it would be nice if I mention how I achieved the feature I was looking for.
Since I was looking for a way to have an asynchronous resolution of callback events (which is by default) and sequential resolution as per the requirement, I had to override the callbacks method of AsyncMachine.
Method 1:
import asyncio
from functools import partial
from transitions.extensions.asyncio import AsyncMachine
class EnhancedMachine(AsyncMachine):
async def callbacks(self, funcs, event_data):
""" Overriding callbacks method:
Get `parallel_callback` keyword argument to decide whether
callback events should be resolved in parallel or in sequence.
"""
parallel_callback = event_data.kwargs.get('parallel_callback', None)
resolved_funcs = [partial(event_data.machine.callback, func, event_data) for func in funcs]
if parallel_callback is False:
for func in resolved_funcs:
await func()
else:
await self.await_all(resolved_funcs)
class Model:
STATES = ['A', 'B']
TRANSITIONS = [
{'trigger': 'next', 'source': 'A', 'dest': 'B',
'prepare': ['initialize1', 'initialize2', 'initialize3'], 'before': [], 'after': ['show_attributes']}
]
def __init__(self, name, state='initial'):
self.name = name
self.state = state
self.sequential_transition = True
self.attribute_1 = None
self.attribute_2 = None
self.attribute_3 = None
async def initialize1(self, ed):
await asyncio.sleep(1) # This is expensive operation and will take some time.
self.attribute_1 = '1Done'
print(f'{self.name} {self.state} -> Initialized1: ', self.attribute_1)
async def initialize2(self, ed):
await asyncio.sleep(0.5) # This is expensive operation and will take some time.
self.attribute_2 = f'{self.attribute_1}_2Done'
print(f'{self.name} {self.state} -> Initialized2: ', self.attribute_2)
async def initialize3(self, ed):
self.attribute_3 = f'{self.attribute_2}_3Done'
print(f'{self.name} {self.state} -> Initialized3: ', self.attribute_3)
async def show_attributes(self, ed):
print(f'{self.name} {self.state} -> Showing all: {self.attribute_3}')
machine = EnhancedMachine(
model=None,
states=Model.STATES,
transitions=Model.TRANSITIONS,
initial=None,
send_event=True, # this will pass EventData instance for each method.
queued='model'
# queued=True
)
async def main():
model1 = Model(name='Model1', state='A')
machine.add_model(model1, initial=model1.state)
# Passing `parallel_callback` as False for synchronous events
await machine.dispatch('next', parallel_callback=False)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
Drawbacks:
send_event=True is added and all the method definitions have been added with extra argument ed (event_data) to handle the parallel_callback keyword argument.
Transition callback requires passing parallel_callback=False and has to change all possible places in the code.
If the next transition has to be decided from the definition of the transition itself, then the keyword argument parallel_callback can not be passed (at least I'm not sure how to do this):
TRANSITIONS = [
{'trigger': 'next', 'source': 'A', 'dest': 'B',
'prepare': [], 'before': [], 'after': ['next2']},
{'trigger': 'next2', 'source': 'B', 'dest': 'C',
'prepare': ['initialize1', 'initialize2', 'initialize3'], 'before': [], 'after': ['show_attributes']}
]
Method 2 (I personally prefer this way):
In the definition of transitions, grouping the callbacks together which are dependent on each other and should be resolved sequentially.
Using this method, final transitions would look something like this:
TRANSITIONS = [
{'trigger': 'next', 'source': 'A', 'dest': 'B',
'prepare': [('initialize1', 'initialize2', 'initialize3')], 'before': [],
'after': ['show_attributes']}
]
Explanation:
'prepare': [('callback1', 'callback2'), 'callback3']
Here group1 (callback1 and callback2), group2 (callback3) will be resolved asynchronously (parallelly). But callback1 and callback2 in group1 will be resolved synchronously (sequentially).
Overriden callbacks method will look slightly different now along with a new static method await_sequential:
class EnhancedMachine(AsyncMachine):
async def callbacks(self, func_groups, event_data):
""" Triggers a list of callbacks """
resolved_func_groups = []
for funcs in func_groups:
if isinstance(funcs, (list, tuple)):
resolved_funcs = [partial(event_data.machine.callback, func, event_data) for func in funcs]
else:
resolved_funcs = [partial(event_data.machine.callback, funcs, event_data)]
resolved_func_groups.append(resolved_funcs)
# await asyncio.gather(*[self.await_sequential(funcs) for funcs in resolved_func_groups])
await self.await_all([partial(self.await_sequential, funcs) for funcs in resolved_func_groups])
#staticmethod
async def await_sequential(funcs):
return [await func() for func in funcs]
Cons:
Nothing changed in the definition of the methods and the method calls.
Changed one place and it fixed all the places.
Drawbacks:
You should be aware of what your methods are doing. Sometimes unwanted grouping will cause unnecessary delays in the resolution of the events.
Using both ways, I got the same desired output:
Model1 A -> Initialized1: 1Done
Model1 A -> Initialized2: 1Done_2Done
Model1 A -> Initialized3: 1Done_2Done_3Done
Model1 B -> Showing all: 1Done_2Done_3Done
I'm sticking with the 2nd approach though I'd be happy to know other efficient ways to implement such a feature :)

I think your 'method 2' looks alright. If you know that all callbacks should be executed sequentially and you do not need parallel execution at all, you could also just override await_all with:
class EnhancedMachine(AsyncMachine):
#staticmethod
async def await_all(callables):
return [await func() for func in callables]
If you switch the meaning of tuples/lists, you could shorten the code a bit to something like this:
class EnhancedMachine(AsyncMachine):
async def callbacks(self, func_groups, event_data):
results = []
for funcs in func_groups:
if isinstance(funcs, (list, tuple)):
results.extend(await self.await_all(
[partial(event_data.machine.callback, func, event_data)
for func in funcs]
))
else:
results.append(await self.callback(funcs, event_data))
return results
This enables callback annotation like [stage_1, (stage_2a, stage_2b, stage_2c), stage_3] where each stage is sequentially executed but sub-stages are called in parallel.

Related

API JSON Schema Validation with Optional Element using Pydantic

I am using fastapi and BaseModel from pydantic to validate and document the JSON schema for an API return.
This works well for a fixed return but I have optional parameters that change the return so I would like to include it in the validation but for it not to fail when the parameter is missing and the field is not returned in the API.
For example: I have an optional boolean parameter called transparency when this is set to true I return a block called search_transparency with the elastic query returned.
{
"info": {
"totalrecords": 52
},
"records": [],
"search_transparency": {"full_query": "blah blah"}
}
If the parameter transparency=true is not set I want the return to be:
{
"info": {
"totalrecords": 52
},
"records": []
}
However, when I set that element to be Optional in pydantic, I get this returned instead:
{
"info": {
"totalrecords": 52
},
"records": [],
"search_transparency": None
}
I have something similar for the fields under records. The default is a minimal return of fields but if you set the parameter full=true then you get many more fields returned. I would like to handle this in a similar way with the fields just being absent rather than shown with a value of None.
This is how I am handling it with pydantic:
class Info(BaseModel):
totalrecords: int
class Transparency(BaseModel):
full_query: str
class V1Place(BaseModel):
name: str
class V1PlaceAPI(BaseModel):
info: Info
records: List[V1Place] = []
search_transparency: Optional[Transparency]
and this is how I am enforcing the validation with fastapi:
#app.get("/api/v1/place/search", response_model=V1PlaceAPI, tags=["v1_api"])
I have a suspicion that maybe what I am trying to achieve is poor API practice, maybe I am not supposed to have variable returns.
Should I instead be creating multiple separate endpoints to handle this?
eg. api/v1/place/search?q=test vs api/v1/place/full/transparent/search?q=test
EDIT
More detail of my API function:
#app.get("/api/v1/place/search", response_model=V1PlaceAPI, tags=["v1_api"])
def v1_place_search(q: str = Query(None, min_length=3, max_length=500, title="search through all place fields"),
transparency: Optional[bool] = None,
offset: Optional[int] = Query(0),
limit: Optional[int] = Query(15)):
search_limit = offset + limit
results, transparency_query = ESQuery(client=es_client,
index='places',
transparency=transparency,
track_hits=True,
offset=offset,
limit=search_limit)
return v1_place_parse(results.to_dict(),
show_transparency=transparency_query)
where ESQuery just returns an elasticsearch response.
And this is my parse function:
def v1_place_parse(resp, show_transparency=None):
"""This takes a response from elasticsearch and parses it for our legacy V1 elasticapi
Args:
resp (dict): This is the response from Search.execute after passing to_dict()
Returns:
dict: A dictionary that is passed to API
"""
new_resp = {}
total_records = resp['hits']['total']['value']
query_records = len(resp.get('hits', {}).get('hits', []))
new_resp['info'] = {'totalrecords': total_records,
'totalrecords_relation': resp['hits']['total']['relation'],
'totalrecordsperquery': query_records,
}
if show_transparency is not None:
search_string = show_transparency.get('query', '')
new_resp['search_transparency'] = {'full_query': str(search_string),
'components': {}}
new_resp['records'] = []
for hit in resp.get('hits', {}).get('hits', []):
new_record = hit['_source']
new_resp['records'].append(new_record)
return new_resp
Probably excluding that field if it is None can get the job done.
Just add a response_model_exclude_none = True as a path parameter
#app.get(
"/api/v1/place/search",
response_model=V1PlaceAPI,
tags=["v1_api"],
response_model_exclude_none=True,
)
You can customize your Response model even more, here is a well explained answer of mine I really suggest you check it out.

Concatenating Future Lists in flutter

I am reading from 3 separate .json files via futures and want to concatenate them into one list. However, I dont't know how to, since .add and + seem to not be defined for future Lists. I also struggle to use then for further concatenation.
I want to return a Future with values from all 3 json files.
Code:
Future<List<Furniture>> getAllHousewares() {
if (_allHousewares != null) {
return _allHousewares;
}
_allHousewares = rootBundle.loadString("res/raw/housewares.json").then((json) =>
(jsonDecode(json) as List).map((houseware) => Furniture.fromJson(houseware)).toList());
_allWallmounteds = rootBundle.loadString("res/raw/Wall-mounted.json").then((json) =>
(jsonDecode(json) as List).map((wallmounted) => Furniture.fromJson(wallmounted)).toList());
_allMiscellaneouss = rootBundle.loadString("res/raw/miscellaneous.json").then((json) =>
(jsonDecode(json) as List).map((miscellaneous) => Furniture.fromJson(miscellaneous)).toList());
return _allHousewares;
}
For this case, the best way to handle it is to use the wait static method of the Future class. The method:
Waits for multiple futures to complete and collects their results.
Since all of your loaded JSON strings contain the same datatype, you can handle them all the same way with loops which leads to cleaner code. This code uses await, which is just syntactic sugar for .then.
After the data for each json is processed, it's concatenated with expand, though it could have been easily done with the + operator as well.
Example:
Future<List<Furniture>> getAllHousewares() async {
if (_allHousewares != null) {
return _allHousewares;
}
Future _allHousewares = rootBundle.loadString("res/raw/housewares.json");
Future _allWallmounteds = rootBundle.loadString("res/raw/Wall-mounted.json");
Future _allMiscellaneouss = rootBundle.loadString("res/raw/miscellaneous.json");
List combined = await Future.wait([_allHousewares, _allWallmounteds, _allMiscellaneouss ]);
List<List<Furniture>> tempFurniture= List();
for(String json in combined) {
tempFurniture.add((jsonDecode(json) as List).map((thingInList) => Furniture.fromJson(thingInList)).toList());
}
List<Furniture> furniture = tempFurniture.expand((item) => item).toList();
return furniture;
}

How do you update the CanExecute value after the ReactiveCommand has been declared

I am using ReactiveUI with AvaloniaUI and have a ViewModel with several ReactiveCommands namely Scan, Load, and Run.
Scan is invoked when an Observable<string> is updated (when I receive a barcode from a scanner).
Load is triggered from within the Scan command.
Run is triggered from a button on the UI.
Simplified code below:
var canRun = Events.ToObservableChangeSet().AutoRefresh().ToCollection().Select(x => x.Any());
Run = ReactiveCommand.CreateFromTask<bool>(EventSuite.RunAsync, canRun);
var canLoad = Run.IsExecuting.Select(x => x == false);
var Load = ReactiveCommand.CreateFromTask<string, Unit>(async (barcode) =>
{
//await - go off and load Events.
}, canLoad);
var canReceiveScan = Load.IsExecuting.Select(x => x == false)
.Merge(Run.IsExecuting.Select(x => x == false));
var Scan = ReactiveCommand.CreateFromTask<string, Unit>(async (barcode) =>
{
//do some validation stuff
await Load.Execute(barcode)
}, canReceiveScan);
Barcode
.SubscribeOn(RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(Scan);
Each command can only be executed if no other command is running (including itself). But I can't reference the commands' IsExecuting property before is it declared. So I have been trying to merge the "CanExecute" observable variables like so:
canRun = canRun
.Merge(Run.IsExecuting.Select(x => x == false))
.Merge(Load.IsExecuting.Select(x => x == false))
.Merge(Scan.IsExecuting.Select(x => x == false))
.ObserveOn(RxApp.MainThreadScheduler);
// same for canLoad and canScan
The issue I'm having is that the ReactiveCommand will continue to execute when another command is executing.
Is there a better/correct way to implement this?
But I can't reference the commands' IsExecuting property before is it declared.
One option is to use a Subject<T>, pass it as the canExecute: parameter to the command, and later emit new values using OnNext on the Subject<T>.
Another option is to use WhenAnyObservable:
this.WhenAnyObservable(x => x.Run.IsExecuting)
// Here we get IObservable<bool>,
// representing the current execution
// state of the command.
.Select(executing => !executing)
Then, you can apply the Merge operator to the observables generated by WhenAnyObservable. To skip initial null values, if any, use either the Where operator or .Skip(1).
To give an example of the Subject<T> option described in the answer by Artyom, here is something inspired by Kent Boogaart's book p. 82:
var canRun = new BehaviorSubject<bool>(true);
Run = ReactiveCommand.Create...(..., canExecute: canRun);
Load = ReactiveCommand.Create...(..., canExecute: canRun);
Scan = ReactiveCommand.Create...(..., canExecute: canRun);
Observable.Merge(Run.IsExecuting, Load.IsExecuting, Scan.IsExecuting)
.Select(executing => !executing).Subscribe(canRun);

How to stop Coffeescript from escaping keywords?

I am trying to write a indexeddb function "delete". It should read like this in JS:
var transaction = db.transaction('objectStore','readwrite');
var objectStore = transaction.objectStore('objectStore');
objectStore.delete(id);
However, when I write it in CS:
transaction = db.transaction 'objectStore','readWrite'
objectStore = transaction.objectStore 'objectStore'
objectStore.delete(id)
Of course it outputs:
...
objectStore["delete"](id);
I didn't write a method for IDBTransaction called "delete", but I have to use it. How can I keep CS from escaping the "delete" method and turning it into a "delete" key in an object?
Use backticks to pass through bare Javascript:
`objectStore.delete(id)`
will be compiled through verbatim. Try it here at my favorite site for interpreting between CS and JS: http://js2coffee.org/#coffee2js
transaction = db.transaction 'objectStore','readWrite'
objectStore = transaction.objectStore 'objectStore'
`objectStore.delete(id)`
becomes
var objectStore, transaction;
transaction = db.transaction('objectStore', 'readWrite');
objectStore = transaction.objectStore('objectStore');
objectStore.delete(id);
Why do you care that the JavaScript version is objectStore["delete"](id)? That's the same as objectStore.delete(id).
For example, if you say this in CoffeeScript:
class B
m: (x) -> console.log("B.m(#{x})")
class C extends B
c = new C
c.m('a')
c['m']('b')
The last two lines come out as this JavaScript:
c.m('a');
c['m']('b');
but they both call the same method.
Demo: http://jsfiddle.net/ambiguous/XvNzB/
Similarly, if you say this in JavaScript:
var o = {
m: function(x) { console.log('m', x) }
};
o.m('a');
o['m']('b');
The last two lines call the same method.
Demo: http://jsfiddle.net/ambiguous/Y3eUW/

Coffeescript classes, method and instance variables

I'm going to implement some kind of a Pagniator class in Coffeescript. The Paginator class should hold the information about currentPage, maxPages, columnNames, ...
So my first approach is this:
class Paginator
currentPage = -1
rowCount = -1
pageSize= -1
columnNames = null
constructor: (#config) ->
setup: ->
#config.ajax(
cache: false
type: "GET"
contentType: "application/json"
dataType: "json"
success: (data) =>
this.configurationReceived(data)
)
configurationReceived: (data) =>
this.storeConfig(data)
this.setupGUI()
this.loadPage(1)
$('.pagination ul li').click( ->
Paginator.loadPage($(this).text())
return false
)
storeConfig: (jsonData) =>
rowCount = jsonData['rowAmount']
pageSize = jsonData['pageSize']
columns = jsonData['columns']
return
The #config is a jsRoutes.controllers.xxx from Play 2.0 Framework jsroutes object.
On Page load I do
paginator = new Paginator jsRoutes.controllers.PlayerController.paginatorConfiguration()
paginator.setup()
But I get a "this.storeConfig is not a function" all the time. Can someone help me on that? Do I misuse the class syntax here? My aim is to have the state of the Paginator encapsulated in a Paginator object (instance). On startup I want to do some initialization stuff that is done via a AJAX call to a "route" which is a HTTP endpoint.
Thanks
There seems to be a problem with the indentation here:
$('.pagination ul li').click( ->
Paginator.loadPage($(this).text())
return false
) # <---
should be
$('.pagination ul li').click( ->
Paginator.loadPage($(this).text())
return false
)
Because of this, the following code containing the definition of the method "storeConfig" is not part of the class, therefore "this.storeConfig" is not a function.
You can easily see this if you copy-past your code into the "Try-coffescript" form at coffeescript.org and examine the JavaScript output.