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