Added `Procedure.to_dict` method.

Also, LINT+black for the `procedure` module.
This commit is contained in:
Fabio Manganiello 2023-12-09 01:23:36 +01:00
parent a6d6fd4067
commit 3ffaaa0eb9
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
1 changed files with 168 additions and 83 deletions

View File

@ -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()