How to generate outputs from PyTransitions FSM? - pytransitions

I am using PyTransitions to generate a simple FSM, configuring it using a yml file.
An example could be something like this:
initial: A
states:
- A
- B
- C
transitions:
- {trigger: "AtoC", source: "A", dest: "C"}
- {trigger: "CtoB", source: "C", dest: "B"}
My question is, using the yml file, how do I write in state outputs? For example, at state A turn on LED 1, state B turn on LED2, state C turn on LED1 and 2. I can't find any documentation for it in the PyTransitions page.

My question is, using the yml file, how do I write in state outputs?
There is no dedicated output slot for states but you can use transitions to trigger certain actions when a state is entered or left.
Actions can be done in callbacks. Those actions need to be implemented in the model. If you want to do most things via configurations you can customize Machine and the used State class. For instance, you could create a custom LEDState and assign a value array called led_states to it where each value represents a LED state. The array is applied to the LEDs in the after_state_change callback of the Model that is called update_leds:
from transitions import Machine, State
class LEDState(State):
ALL_OFF = [False] * 2 # number of LEDs
def __init__(self, name, on_enter=None, on_exit=None,
ignore_invalid_triggers=None, led_states=None):
# call the base class constructor without 'led_states'...
super().__init__(name, on_enter, on_exit, ignore_invalid_triggers)
# ... and assign its value to a custom property
# when 'led_states' is not passed, we assign the default
# setting 'ALL_OFF'
self.led_states = led_states if led_states is not None else self.ALL_OFF
# create a custom machine that uses 'LEDState'
# 'LEDMachine' will pass all parameters in the configuration
# dictionary to the constructor of 'LEDState'.
class LEDMachine(Machine):
state_cls = LEDState
class LEDModel:
def __init__(self, config):
self.machine = LEDMachine(model=self, **config, after_state_change='update_leds')
def update_leds(self):
print(f"---New State {self.state}---")
for idx, led in enumerate(self.machine.get_state(self.state).led_states):
print(f"Set LED {idx} {'ON' if led else 'OFF'}.")
# using a dictionary here instead of YAML
# but the process is the same
config = {
'name': 'LEDModel',
'initial': 'Off',
'states': [
{'name': 'Off'},
{'name': 'A', 'led_states': [True, False]},
{'name': 'B', 'led_states': [False, True]},
{'name': 'C', 'led_states': [True, True]}
]
}
model = LEDModel(config)
model.to_A()
# ---New State A---
# Set LED 0 ON.
# Set LED 1 OFF.
model.to_C()
# ---New State C---
# Set LED 0 ON.
# Set LED 1 ON.
This is just one way how this could be done. You could also just pass an array with indexes representing all LEDs that should be ON. We'd switch off all LEDs when exiting a state with before_state_change
ALL_OFF = []
# ...
self.machine = LEDMachine(model=self, **config, before_state_change='reset_leds', after_state_change='set_lets')
# ...
def reset_leds(self):
for led_idx in range(num_leds):
print(f"Set {led_idx} 'OFF'.")
def set_lets(self):
for led_idx in self.machine.get_state(self.state).led_states:
print(f"Set LED {led_idx} 'ON'.")
# ...
{'name': 'A', 'led_states': [0]},
{'name': 'B', 'led_states': [1]},
{'name': 'C', 'led_states': [0, 1]}

Related

Referencing a loop object

i am currently checking out tanka + jsonnet. But evertime i think i understand it... sth. new irritates me. Can somebody help me understand how to do a loop-reference? (Or general better solution?)
Trying to create multiple deployments with a corresponding configmapVolumeMount and i am not sure how to reference to the according configmap object here?
(using a configVolumeMount it works since it refers to the name, not the object).
deployment: [
deploy.new(
name='demo-' + instance.name,
],
)
+ deploy.configMapVolumeMount('config-' + instance.name, '/config.yml', k.core.v1.volumeMount.withSubPath('config.yml'))
for instance in $._config.demo.instances
],
configMap: [
configMap.new('config-' + instance.name, {
'config.yml': (importstr 'files/config.yml') % {
name: instance.name,
....
},
}),
for instance in $._config.demo.instances
]
regards
Great to read that you're making progress with tanka, it's an awesome tool (once you learned how to ride it heh).
Find below a possible answer, see inline comments in the code, in particular how we ab-use tanka layout flexibility, to "populate" deploys: [...] array with jsonnet objects containing each paired deploy+configMap.
config.jsonnet
{
demo: {
instances: ['foo', 'bar'],
image: 'nginx', // just as example
},
}
main.jsonnet
local config = import 'config.jsonnet';
local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';
{
local deployment = k.apps.v1.deployment,
local configMap = k.core.v1.configMap,
_config:: import 'config.jsonnet',
// my_deploy(name) will return name-d deploy+configMap object
my_deploy(name):: {
local this = self,
deployment:
deployment.new(
name='deploy-%s' % name,
replicas=1,
containers=[
k.core.v1.container.new('demo-%s' % name, $._config.demo.image),
],
)
+ deployment.configMapVolumeMount(
this.configMap,
'/config.yml',
k.core.v1.volumeMount.withSubPath('config.yml')
),
configMap:
configMap.new('config-%s' % name)
+ configMap.withData({
// NB: replacing `importstr 'files/config.yml';` by
// a simple YAML multi-line string, just for the sake of having
// a simple yet complete/usable example.
'config.yml': |||
name: %(name)s
other: value
||| % { name: name }, //
}),
},
// Tanka is pretty flexible with the "layout" of the Kubernetes objects
// in the Environment (can be arrays, objects, etc), below using an array
// for simplicity (built via a loop/comprehension)
deploys: [$.my_deploy(name) for name in $._config.demo.instances],
}
output
$ tk init
[...]
## NOTE: using https://kind.sigs.k8s.io/ local Kubernetes cluster
$ tk env set --server-from-context kind-kind environments/default
[... save main.jsonnet, config.jsonnet to ./environments/default/]
$ tk apply --dry-run=server environments/default
[...]
configmap/config-bar created (server dry run)
configmap/config-foo created (server dry run)
deployment.apps/deploy-bar created (server dry run)
deployment.apps/deploy-foo created (server dry run)

SoftLayer_Virtual_Guest setTransientWebhook

I'm looking for a sample code using SoftLayer_Virtual_Guest::setTransientWebhook for Transient VSI. Is there a sample code?
Thanks
Behzad
Try with next requests:
Method GET
http://api.softlayer.com/rest/v3.1/SoftLayer_Account/getVirtualGuests?objectMask=mask[id,transientGuestFlag]&objectFilter={"virtualGuests":{"transientGuestFlag":{"operation": 1}}}
Data 1 means true also 0 means false, in this case, we use one with data 1
Choose the VSI id you want to set and use the following request:
Method POST
http://api.softlayer.com/rest/v3.1/SoftLayer_Virtual_Guest/111111/setTransientWebhook
Body
{"parameters":[
"https://test1.com",
"testsupport"]
}
The 111111 data is the VSI that we chose in the previous request.
Reference
https://sldn.softlayer.com/reference/datatypes/SoftLayer_Virtual_Guest/#transientGuestFlag
https://sldn.softlayer.com/reference/services/SoftLayer_Virtual_Guest/setTransientWebhook/
I hope it helps you
Thanks Daniel, would this be equivalent code in python: request = client['Virtual_Guest'].createObject({
'hostname': hostname,
'domain': domainname,
'startCpus': cpus,
'maxMemory': memory,
'hourlyBillingFlag': 'true',
'datacenter': {'name': 'tor01'},
'operatingSystemReferenceCode': os_code,
# 'localDiskFlag': 'false',
"supplementalCreateObjectOptions": {"flavorKeyName": "C1_1X1X25"},
'transientGuestFlag': 'true',
# 'sshKeys': [{"id":201867}]
"parameters":["https://test1.com","testsupport"].

How to round trip ruamel.yaml strings like "on"

When using ruamel.yaml to round-trip some YAML I see the following issue. Given this input:
root:
matchers:
- select: "response.body#state"
test: all
expected: "on"
I see this output:
root:
matchers:
- select: response.body#state
test: all
expected: on
Note that in YAML, on parses as a boolean true value while off parses as false.
The following code is used to read/write:
# Use the default (round-trip) settings.
yaml = YAML()
if args.source == '-':
src = sys.stdin
else:
src = open(args.source)
doc = yaml.load(src)
process(args.tag, set(args.keep.split(',')), doc)
if args.destination == '-':
dest = sys.stdout
else:
dest = open(args.destination, 'w')
yaml.dump(doc, dest)
The process function is not modifying values. It only removes things with a special tag in the input after crawling the structure.
How can I get the output to be a string rather than a boolean?
You write that:
Note that in YAML, on parses as a boolean true value while off parses as false.
That statement is not true (or better:
has not been true for ten years). If you have an unquoted on in your
YAML, like in your output, that is obviously not the case when using ruamel.yaml:
import sys
import ruamel.yaml
yaml_str = """\
root:
matchers:
- select: response.body#state
test: all
expected: on
"""
yaml = ruamel.yaml.YAML()
data = yaml.load(yaml_str)
expected = data['root']['matchers'][0]['expected']
print(type(expected), repr(expected))
which gives:
<class 'str'> 'on'
This is because in the YAML 1.2 spec on/off/yes/no are no
longer mentioned as having the same meaning as true
resp. false. They are mentioned in the YAML 1.1 spec, but that was
superseded in 2009. Unfortunately there are YAML libraries out in the
wild, that have not been updated since then.
What is actually happening is that the suprefluous quotes in your
input are automatically discarded by the round-trip process. You can
also see that happen for the value "response.body#state". Although
there the character that starts comments (#) is included, to
actually start a comment that character has to be proceded by
white-space, and since it is isn't, the quotes are not necessary.
So your output is fine, but if you are in the unfortunate
situation where you have to deal with other programs relying on
outdated YAML 1.1, then you can e.g. specify that you want to preserve
your quotes on round-trip:
yaml_str = """\
root:
matchers:
- select: "response.body#state"
test: all
expected: "on"
"""
yaml = ruamel.yaml.YAML()
yaml.indent(sequence=4, offset=2)
yaml.preserve_quotes = True
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)
as this gives your exact input:
root:
matchers:
- select: "response.body#state"
test: all
expected: "on"
However maybe the better option would be that you actually specify that your
YAML is and has to conforming to the YAML 1.1 specification by making
your intensions, and the output document, explicit:
yaml_str = """\
root:
matchers:
- select: response.body#state
test: all
expected: on
"""
yaml_in = ruamel.yaml.YAML()
yaml_out = ruamel.yaml.YAML()
yaml_out.indent(sequence=4, offset=2)
yaml_out.version = (1, 1)
data = yaml_in.load(yaml_str)
yaml_out.dump(data, sys.stdout)
Notice that the "unquoted" YAML 1.2 input, gives output where on is quoted:
%YAML 1.1
---
root:
matchers:
- select: response.body#state
test: all
expected: 'on'

watchman: I am missing file deletions happening before subscription

I am missing deletes in watchman. Version 4.9.0, inotify.
My test code:
#!/usr/bin/env python3
import pathlib
import pywatchman
w = pywatchman.client()
w.query('watch', '/tmp/z')
clock = w.query('clock', '/tmp/z')['clock']
print(clock)
q = w.query('subscribe', '/tmp/z', 'Buffy', {'expression':["since", clock],
"fields": ["name", "exists", "oclock", "ctime_ns", "new", "mode"]})
print(q)
f = pathlib.Path('/tmp/z/xx')
f.touch()
data = w.receive()
clock = data['clock']
print()
print('Touch file:')
print(data)
print('Clock:', clock)
f.unlink()
print()
print('Delete file:')
print(w.receive())
w.close()
w = pywatchman.client(timeout=99999)
q = w.query('subscribe', '/tmp/z', 'Buffy', {'expression':["since", clock],
"fields": ["name", "exists", "oclock", "ctime_ns", "new", "mode"]})
print(q)
print()
print('We request changes since', clock)
print(w.receive())
w.close()
What I am seeing:
We create the file. We receive the notification of the new file and the directory change. GOOD. We take note of the "clock" of this notification.
We delete the file. We get the notification of the file deletion. GOOD. Be DO NOT get the notification of the directory change.
Just imagine now that the process crashes BEFORE it can update the internal details, but it remember the changes notified in step 1 (directory update and creation of a new file). That is, transaction 1 is processed, but the program crashes before transaction 2 is processed.
We now open a new subscription to watchman (remember, we are simulating a crash) and request changes since step 1. I am simulating a recovery, where the program reboots, notice that transaction 1 was OK (the file is present) and request more changes (it should get the deletion).
I would expect to get a file deletion but I get... NOTHING. CATASTROPHIC.
Transcript:
$ ./watchman-bug.py
c:1517109517:10868:3:23
{'clock': 'c:1517109517:10868:3:23', 'subscribe': 'Buffy', 'version': '4.9.0'}
Touch file:
{'unilateral': True, 'subscription': 'Buffy', 'root': '/tmp/z', 'files': [{'name': 'xx', 'exists': True, 'oclock': 'c:1517109517:10868:3:24', 'ctime_ns': 1517114230070245747, 'new': True, 'mode': 33188}], 'is_fresh_instance': False, 'version': '4.9.0', 'since': 'c:1517109517:10868:3:23', 'clock': 'c:1517109517:10868:3:24'}
Clock: c:1517109517:10868:3:24
Delete file:
{'unilateral': True, 'subscription': 'Buffy', 'root': '/tmp/z', 'files': [{'name': 'xx', 'exists': False, 'oclock': 'c:1517109517:10868:3:25', 'ctime_ns': 1517114230070245747, 'new': False, 'mode': 33188}], 'is_fresh_instance': False, 'version': '4.9.0', 'since': 'c:1517109517:10868:3:24', 'clock': 'c:1517109517:10868:3:25'}
{'clock': 'c:1517109517:10868:3:25', 'subscribe': 'Buffy', 'version': '4.9.0'}
We request changes since c:1517109517:10868:3:24
The process hangs expecting the deletion notification.
What am I doing wrong?.
Thanks for your time and knowledge!
The issue is that you're using a since expression term rather than informing watchman to use the since generator (the recency index).
What's the difference? You can think of this as the difference between the FROM and WHERE clauses in SQL. The expression field is similar in intent to the WHERE clause: it applies to the matched results and filters them down, but what you wanted to do is specify the FROM clause by setting the since field in the query spec. This is admittedly a subtle difference.
The solution is to remove the expression term and add the generator term like this:
q = w.query('subscribe', '/tmp/z', 'Buffy',
{"since": clock,
"fields": ["name", "exists", "oclock",
"ctime_ns", "new", "mode"]})
While we don't have really any documentation on the use of the pywatchman API, you can borrow the concepts from the slightly better documented nodejs API; here's a relevant snippet:
https://facebook.github.io/watchman/docs/nodejs.html#subscribing-only-to-changed-files

dotcloud supervisord.conf file environment specification

http://docs.dotcloud.com/guides/daemons/ states:
Configuring The Environment
You can easily modify the environment of execution of your daemon with the “directory” and “environment” directives to change the directory where the command is executed and to define additional environment variable. For example:
[program:daemonname]
command = php my_daemon.php
directory = /home/dotcloud/current/
environment = QUEUE=*, VERBOSE=TRUE
However, I'm finding my PYTHONPATH environment variable is not being set:
dotcloud.yml:
www:
type: python
db:
type: postgresql
worker:
type: python-worker
supervisord.conf:
[program:apnsd]
command=/home/dotcloud/current/printenv.py
environment=PYTHONPATH=/home/dotcloud/current/apnsd/
printenv.py
#! /home/dotcloud/env/bin/python
import os
print "ENVIRONMENT"
print os.environ
the logs:
ENVIRONMENT
{'SUPERVISOR_ENABLED': '1', 'SUPERVISOR_SERVER_URL': 'unix:///var/dotcloud/super
visor.sock', 'VERBOSE': 'no', 'UPSTART_INSTANCE': '', 'PYTHONPATH': '/', 'PREVLE
VEL': 'N', 'UPSTART_EVENTS': 'runlevel', '/': '/', 'SUPERVISOR_PROCESS_NAME': 'a
pnsd', 'UPSTART_JOB': 'rc', 'PWD': '/', 'SUPERVISOR_GROUP_NAME': 'apnsd', 'RUNLE
VEL': '2', 'PATH': '/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
', 'runlevel': '2', 'previous': 'N'}
Do not show a modified python variable!
There is a bug in Supervisor; some variables (like those containing a /) have to be quoted.
In that case, you need:
[program:apnsd]
command=/home/dotcloud/current/printenv.py
environment= PYTHONPATH="/home/dotcloud/current/apnsd/"
(The space in = PYTHONPATH is not mandatory, it's just to make the file slightly more readable; the quotes around the value of PYTHONPATH are, however, required!)
I will update dotCloud's documentation to mention this issue.