forked from platypush/platypush
Added Procedure.to_dict
method.
Also, LINT+black for the `procedure` module.
This commit is contained in:
parent
a6d6fd4067
commit
3ffaaa0eb9
1 changed files with 168 additions and 83 deletions
|
@ -19,14 +19,14 @@ class Statement(enum.Enum):
|
|||
RETURN = 'return'
|
||||
|
||||
|
||||
class Procedure(object):
|
||||
""" Procedure class. A procedure is a pre-configured list of requests """
|
||||
class Procedure:
|
||||
"""Procedure class. A procedure is a pre-configured list of requests"""
|
||||
|
||||
def __init__(self, name, _async, requests, args=None, backend=None):
|
||||
"""
|
||||
Params:
|
||||
name -- Procedure name
|
||||
_async -- Whether the actions in the procedure are supposed to
|
||||
_async -- Whether the actions in the procedure are supposed to
|
||||
be executed sequentially or in parallel (True or False)
|
||||
requests -- List of platypush.message.request.Request objects
|
||||
"""
|
||||
|
@ -42,7 +42,17 @@ class Procedure(object):
|
|||
req.backend = self.backend
|
||||
|
||||
@classmethod
|
||||
def build(cls, name, _async, requests, args=None, backend=None, id=None, procedure_class=None, **kwargs):
|
||||
def build(
|
||||
cls,
|
||||
name,
|
||||
_async,
|
||||
requests,
|
||||
args=None,
|
||||
backend=None,
|
||||
id=None,
|
||||
procedure_class=None,
|
||||
**kwargs,
|
||||
):
|
||||
reqs = []
|
||||
for_count = 0
|
||||
while_count = 0
|
||||
|
@ -64,25 +74,28 @@ class Procedure(object):
|
|||
|
||||
if m:
|
||||
if_count += 1
|
||||
if_name = '{}__if_{}'.format(name, if_count)
|
||||
if_name = f'{name}__if_{if_count}'
|
||||
condition = m.group(2)
|
||||
|
||||
if_config.put({
|
||||
'name': if_name,
|
||||
'_async': False,
|
||||
'requests': request_config[key],
|
||||
'condition': condition,
|
||||
'else_branch': [],
|
||||
'backend': backend,
|
||||
'id': id,
|
||||
})
|
||||
if_config.put(
|
||||
{
|
||||
'name': if_name,
|
||||
'_async': False,
|
||||
'requests': request_config[key],
|
||||
'condition': condition,
|
||||
'else_branch': [],
|
||||
'backend': backend,
|
||||
'id': id,
|
||||
}
|
||||
)
|
||||
|
||||
continue
|
||||
|
||||
if key == 'else':
|
||||
if if_config.empty():
|
||||
raise RuntimeError('else statement with no ' +
|
||||
'associated if in {}'.format(name))
|
||||
raise RuntimeError(
|
||||
f'else statement with no associated if in {name}'
|
||||
)
|
||||
|
||||
conf = if_config.get()
|
||||
conf['else_branch'] = request_config[key]
|
||||
|
@ -100,19 +113,23 @@ class Procedure(object):
|
|||
|
||||
if m:
|
||||
for_count += 1
|
||||
loop_name = '{}__for_{}'.format(name, for_count)
|
||||
loop_name = f'{name}__for_{for_count}'
|
||||
|
||||
# A 'for' loop is synchronous. Declare a 'fork' loop if you
|
||||
# want to process the elements in the iterable in parallel
|
||||
_async = True if m.group(1) == 'fork' else False
|
||||
_async = m.group(1) == 'fork'
|
||||
iterator_name = m.group(2)
|
||||
iterable = m.group(3)
|
||||
|
||||
loop = ForProcedure.build(name=loop_name, _async=_async,
|
||||
requests=request_config[key],
|
||||
backend=backend, id=id,
|
||||
iterator_name=iterator_name,
|
||||
iterable=iterable)
|
||||
loop = ForProcedure.build(
|
||||
name=loop_name,
|
||||
_async=_async,
|
||||
requests=request_config[key],
|
||||
backend=backend,
|
||||
id=id,
|
||||
iterator_name=iterator_name,
|
||||
iterable=iterable,
|
||||
)
|
||||
|
||||
reqs.append(loop)
|
||||
continue
|
||||
|
@ -124,13 +141,17 @@ class Procedure(object):
|
|||
|
||||
if m:
|
||||
while_count += 1
|
||||
loop_name = '{}__while_{}'.format(name, while_count)
|
||||
loop_name = f'{name}__while_{while_count}'
|
||||
condition = m.group(1).strip()
|
||||
|
||||
loop = WhileProcedure.build(name=loop_name, _async=False,
|
||||
requests=request_config[key],
|
||||
condition=condition,
|
||||
backend=backend, id=id)
|
||||
loop = WhileProcedure.build(
|
||||
name=loop_name,
|
||||
_async=False,
|
||||
requests=request_config[key],
|
||||
condition=condition,
|
||||
backend=backend,
|
||||
id=id,
|
||||
)
|
||||
|
||||
reqs.append(loop)
|
||||
continue
|
||||
|
@ -147,8 +168,14 @@ class Procedure(object):
|
|||
pending_if = if_config.get()
|
||||
reqs.append(IfProcedure.build(**pending_if))
|
||||
|
||||
# noinspection PyArgumentList
|
||||
return procedure_class(name=name, _async=_async, requests=reqs, args=args, backend=backend, **kwargs)
|
||||
return procedure_class(
|
||||
name=name,
|
||||
_async=_async,
|
||||
requests=reqs,
|
||||
args=args,
|
||||
backend=backend,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _find_nearest_loop(stack):
|
||||
|
@ -175,9 +202,9 @@ class Procedure(object):
|
|||
v = Request.expand_value_from_context(v, **context)
|
||||
args[k] = v
|
||||
context[k] = v
|
||||
logger.info('Executing procedure {} with arguments {}'.format(self.name, args))
|
||||
logger.info('Executing procedure %s with arguments %s', self.name, args)
|
||||
else:
|
||||
logger.info('Executing procedure {}'.format(self.name))
|
||||
logger.info('Executing procedure %s', self.name)
|
||||
|
||||
response = Response()
|
||||
token = Config.get('token')
|
||||
|
@ -202,13 +229,13 @@ class Procedure(object):
|
|||
loop._should_continue = True
|
||||
break
|
||||
|
||||
if isinstance(self, LoopProcedure):
|
||||
if self._should_continue or self._should_break:
|
||||
if self._should_continue:
|
||||
# noinspection PyAttributeOutsideInit
|
||||
self._should_continue = False
|
||||
if isinstance(self, LoopProcedure) and (
|
||||
self._should_continue or self._should_break
|
||||
):
|
||||
if self._should_continue:
|
||||
self._should_continue = False
|
||||
|
||||
break
|
||||
break
|
||||
|
||||
if token:
|
||||
request.token = token
|
||||
|
@ -230,6 +257,14 @@ class Procedure(object):
|
|||
|
||||
return response or Response()
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'requests': self.requests,
|
||||
'args': self.args,
|
||||
'_async': self._async,
|
||||
}
|
||||
|
||||
|
||||
class LoopProcedure(Procedure):
|
||||
"""
|
||||
|
@ -237,7 +272,9 @@ class LoopProcedure(Procedure):
|
|||
"""
|
||||
|
||||
def __init__(self, name, requests, _async=False, args=None, backend=None):
|
||||
super(). __init__(name=name, _async=_async, requests=requests, args=args, backend=backend)
|
||||
super().__init__(
|
||||
name=name, _async=_async, requests=requests, args=args, backend=backend
|
||||
)
|
||||
self._should_break = False
|
||||
self._should_continue = False
|
||||
|
||||
|
@ -264,36 +301,47 @@ class ForProcedure(LoopProcedure):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, name, iterator_name, iterable, requests, _async=False, args=None, backend=None):
|
||||
super(). __init__(name=name, _async=_async, requests=requests, args=args, backend=backend)
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
iterator_name,
|
||||
iterable,
|
||||
requests,
|
||||
_async=False,
|
||||
args=None,
|
||||
backend=None,
|
||||
):
|
||||
super().__init__(
|
||||
name=name, _async=_async, requests=requests, args=args, backend=backend
|
||||
)
|
||||
self.iterator_name = iterator_name
|
||||
self.iterable = iterable
|
||||
|
||||
def execute(self, _async=None, **context):
|
||||
try:
|
||||
iterable = eval(self.iterable)
|
||||
assert hasattr(iterable, '__iter__'), 'Object of type {} is not iterable: {}'.\
|
||||
format(type(iterable), iterable)
|
||||
assert hasattr(
|
||||
iterable, '__iter__'
|
||||
), f'Object of type {type(iterable)} is not iterable: {iterable}'
|
||||
except Exception as e:
|
||||
logger.debug(f'Iterable {self.iterable} expansion error: {e}')
|
||||
iterable = Request.expand_value_from_context(self.iterable, **context)
|
||||
|
||||
response = Response()
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
for item in iterable:
|
||||
if self._should_return:
|
||||
logger.info('Returning from {}'.format(self.name))
|
||||
logger.info('Returning from %s', self.name)
|
||||
break
|
||||
|
||||
if self._should_continue:
|
||||
self._should_continue = False
|
||||
logger.info('Continuing loop {}'.format(self.name))
|
||||
logger.info('Continuing loop %s', self.name)
|
||||
continue
|
||||
|
||||
if self._should_break:
|
||||
self._should_break = False
|
||||
logger.info('Breaking loop {}'.format(self.name))
|
||||
logger.info('Breaking loop %s', self.name)
|
||||
break
|
||||
|
||||
context[self.iterator_name] = item
|
||||
|
@ -327,37 +375,41 @@ class WhileProcedure(LoopProcedure):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, name, condition, requests, _async=False, args=None, backend=None):
|
||||
super(). __init__(name=name, _async=_async, requests=requests, args=args, backend=backend)
|
||||
def __init__(
|
||||
self, name, condition, requests, _async=False, args=None, backend=None
|
||||
):
|
||||
super().__init__(
|
||||
name=name, _async=_async, requests=requests, args=args, backend=backend
|
||||
)
|
||||
self.condition = condition
|
||||
|
||||
@staticmethod
|
||||
def _get_context(**context):
|
||||
for (k, v) in context.items():
|
||||
for k, v in context.items():
|
||||
try:
|
||||
context[k] = eval(v)
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error for {v}: {e}')
|
||||
if isinstance(v, str):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
context[k] = eval('"{}"'.format(re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
||||
context[k] = eval('"' + re.sub(r'(^|[^\\])"', '\1\\"', v) + '"')
|
||||
except Exception as e:
|
||||
logger.warning('Could not parse value for context variable {}={}'.format(k, v))
|
||||
logger.warning('Context: {}'.format(context))
|
||||
logger.warning(
|
||||
'Could not parse value for context variable %s=%s', k, v
|
||||
)
|
||||
logger.warning('Context: %s', context)
|
||||
logger.exception(e)
|
||||
|
||||
return context
|
||||
|
||||
# noinspection DuplicatedCode,PyBroadException
|
||||
def execute(self, _async=None, **context):
|
||||
response = Response()
|
||||
context = self._get_context(**context)
|
||||
for k, v in context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
exec(f'{k}={v}')
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
logger.debug('Evaluation error: %s=%s: %s', k, v, e)
|
||||
|
||||
while True:
|
||||
condition_true = eval(self.condition)
|
||||
|
@ -365,34 +417,32 @@ class WhileProcedure(LoopProcedure):
|
|||
break
|
||||
|
||||
if self._should_return:
|
||||
logger.info('Returning from {}'.format(self.name))
|
||||
logger.info('Returning from %s', self.name)
|
||||
break
|
||||
|
||||
if self._should_continue:
|
||||
self._should_continue = False
|
||||
logger.info('Continuing loop {}'.format(self.name))
|
||||
logger.info('Continuing loop %s', self.name)
|
||||
continue
|
||||
|
||||
if self._should_break:
|
||||
self._should_break = False
|
||||
logger.info('Breaking loop {}'.format(self.name))
|
||||
logger.info('Breaking loop %s', self.name)
|
||||
break
|
||||
|
||||
response = super().execute(**context)
|
||||
|
||||
if response.output:
|
||||
if isinstance(response.output, dict):
|
||||
new_context = self._get_context(**response.output)
|
||||
for k, v in new_context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
if response.output and isinstance(response.output, dict):
|
||||
new_context = self._get_context(**response.output)
|
||||
for k, v in new_context.items():
|
||||
try:
|
||||
exec(f'{k}={v}')
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
class IfProcedure(Procedure):
|
||||
"""
|
||||
Models an if-else construct.
|
||||
|
@ -418,7 +468,17 @@ class IfProcedure(Procedure):
|
|||
cmd: '/path/turn_off_heating.sh'
|
||||
"""
|
||||
|
||||
def __init__(self, name, condition, requests, else_branch=None, args=None, backend=None, id=None, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
condition,
|
||||
requests,
|
||||
else_branch=None,
|
||||
args=None,
|
||||
backend=None,
|
||||
id=None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs['_async'] = False
|
||||
self.condition = condition
|
||||
self.else_branch = else_branch
|
||||
|
@ -435,32 +495,57 @@ class IfProcedure(Procedure):
|
|||
|
||||
reqs.append(req)
|
||||
|
||||
super(). __init__(name=name, requests=reqs, args=args, backend=backend, **kwargs)
|
||||
super().__init__(name=name, requests=reqs, args=args, backend=backend, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def build(cls, name, condition, requests, else_branch=None, args=None, backend=None, id=None, **kwargs):
|
||||
def build(
|
||||
cls,
|
||||
name,
|
||||
condition,
|
||||
requests,
|
||||
else_branch=None,
|
||||
args=None,
|
||||
backend=None,
|
||||
id=None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs['_async'] = False
|
||||
if else_branch:
|
||||
else_branch = super().build(name=name+'__else', requests=else_branch,
|
||||
args=args, backend=backend, id=id,
|
||||
procedure_class=Procedure, **kwargs)
|
||||
else_branch = super().build(
|
||||
name=name + '__else',
|
||||
requests=else_branch,
|
||||
args=args,
|
||||
backend=backend,
|
||||
id=id,
|
||||
procedure_class=Procedure,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return super().build(name=name, condition=condition, requests=requests,
|
||||
else_branch=else_branch, args=args, backend=backend, id=id,
|
||||
**kwargs)
|
||||
return super().build(
|
||||
name=name,
|
||||
condition=condition,
|
||||
requests=requests,
|
||||
else_branch=else_branch,
|
||||
args=args,
|
||||
backend=backend,
|
||||
id=id,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def execute(self, **context):
|
||||
for (k, v) in context.items():
|
||||
for k, v in context.items():
|
||||
try:
|
||||
exec('{}={}'.format(k, v))
|
||||
exec(f'{k}={v}')
|
||||
except Exception as e:
|
||||
logger.debug(f'Evaluation error: {k}={v}: {e}')
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v)))
|
||||
exec('{k}="' + re.sub(r'(^|[^\\])"', '\1\\"', v) + '"')
|
||||
except Exception as e:
|
||||
logger.debug('Could not set context variable {}={}: {}'.format(k, v, str(e)))
|
||||
logger.debug('Context: {}'.format(context))
|
||||
logger.debug(
|
||||
'Could not set context variable %s=%s: %s', k, v, e
|
||||
)
|
||||
logger.debug('Context: %s', context)
|
||||
|
||||
condition_true = eval(self.condition)
|
||||
response = Response()
|
||||
|
|
Loading…
Reference in a new issue