Implemented sequential execution of tasks in procedures, response context parsing and procedure response returned on the bus as well, #37

This commit is contained in:
Fabio Manganiello 2018-01-05 23:20:39 +01:00
parent b7854cb5dd
commit b98fe01352
3 changed files with 79 additions and 19 deletions

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import random import random
import re
import traceback import traceback
from threading import Thread from threading import Thread
@ -60,10 +61,44 @@ class Request(Message):
proc_name = self.action.split('.')[-1] proc_name = self.action.split('.')[-1]
proc_config = Config.get_procedures()[proc_name] proc_config = Config.get_procedures()[proc_name]
proc = Procedure.build(name=proc_name, requests=proc_config, backend=self.backend, id=self.id) proc = Procedure.build(name=proc_name, requests=proc_config, backend=self.backend, id=self.id)
proc.execute(*args, **kwargs) return proc.execute(*args, **kwargs)
def execute(self, n_tries=1, async=True): def _expand_context(self, **context):
args = {}
for (name, value) in self.args.items():
if isinstance(value, str):
parsed_value = ''
while value:
m = re.match('([^\\\]*)\$([\w\d_-]+)(.*)', value)
if m:
context_name = m.group(2)
value = m.group(3)
if context_name in context:
parsed_value += m.group(1) + context[context_name]
else:
parsed_value += m.group(1) + '$' + m.group(2)
else:
parsed_value += value
value = ''
value = parsed_value
args[name] = value
return args
def _send_response(self, response):
if self.backend and self.origin:
self.backend.send_response(response=response, request=self)
else:
logging.info('Response whose request has no ' +
'origin attached: {}'.format(response))
def execute(self, n_tries=1, async=True, **context):
""" """
Execute this request and returns a Response object Execute this request and returns a Response object
Params: Params:
@ -72,18 +107,28 @@ class Request(Message):
response posted on the bus when available (default), response posted on the bus when available (default),
otherwise the current thread will wait for the response otherwise the current thread will wait for the response
to be returned synchronously. to be returned synchronously.
context -- Key-valued context. Example:
context = (group_name='Kitchen lights')
request.args:
- group: $group_name # will be expanded as "Kitchen lights")
""" """
def _thread_func(n_tries): def _thread_func(n_tries):
if self.action.startswith('procedure.'): if self.action.startswith('procedure.'):
return self._execute_procedure(n_tries=n_tries) try:
response = self._execute_procedure(n_tries=n_tries)
finally:
self._send_response(response)
return response
else:
(module_name, method_name) = get_module_and_method_from_action(self.action) (module_name, method_name) = get_module_and_method_from_action(self.action)
plugin = get_plugin(module_name) plugin = get_plugin(module_name)
try: try:
# Run the action # Run the action
response = plugin.run(method=method_name, **self.args) args = self._expand_context(**context)
response = plugin.run(method=method_name, **args)
if response and response.is_error(): if response and response.is_error():
raise RuntimeError('Response processed with errors: {}'.format(response)) raise RuntimeError('Response processed with errors: {}'.format(response))
@ -99,14 +144,7 @@ class Request(Message):
_thread_func(n_tries-1) _thread_func(n_tries-1)
return return
finally: finally:
if async: self._send_response(response)
# Send the response on the backend
if self.backend and self.origin:
self.backend.send_response(response=response, request=self)
else:
logging.info('Response whose request has no ' +
'origin attached: {}'.format(response))
else:
return response return response
if async: if async:

View file

@ -16,8 +16,8 @@ class Response(Message):
""" """
self.target = target self.target = target
self.output = output self.output = self._parse_msg(output)
self.errors = errors self.errors = self._parse_msg(errors)
self.origin = origin self.origin = origin
self.id = id self.id = id
@ -25,6 +25,17 @@ class Response(Message):
""" Returns True if the respopnse has errors """ """ Returns True if the respopnse has errors """
return len(self.errors) != 0 return len(self.errors) != 0
@classmethod
def _parse_msg(cls, msg):
if isinstance(msg, bytes) or isinstance(msg, bytearray):
msg = msg.decode('utf-8')
if isinstance(msg, str):
try: msg = json.loads(msg.strip())
except ValueError as e: pass
return msg
@classmethod @classmethod
def build(cls, msg): def build(cls, msg):
msg = super().parse(msg) msg = super().parse(msg)

View file

@ -2,6 +2,7 @@ import logging
from ..config import Config from ..config import Config
from ..message.request import Request from ..message.request import Request
from ..message.response import Response
class Procedure(object): class Procedure(object):
""" Procedure class. A procedure is a pre-configured list of requests """ """ Procedure class. A procedure is a pre-configured list of requests """
@ -42,8 +43,18 @@ class Procedure(object):
""" """
logging.info('Executing request {}'.format(self.name)) logging.info('Executing request {}'.format(self.name))
context = {}
response = Response()
for request in self.requests: for request in self.requests:
request.execute(n_tries) response = request.execute(n_tries, async=False, **context)
context = { k:v for (k,v) in response.output.items() } \
if isinstance(response.output, dict) else {}
context['output'] = response.output
context['errors'] = response.errors
return response
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: