If a request on a RunnablePlugin throws an exception then we should also restart the plugin upon reload

Plus some Black/LINT chores
This commit is contained in:
Fabio Manganiello 2022-07-25 00:41:08 +02:00
parent c32142c8b5
commit 55671f4aff
Signed by: blacklight
GPG key ID: D90FBA7F76362774

View file

@ -12,8 +12,12 @@ from platypush.config import Config
from platypush.context import get_plugin from platypush.context import get_plugin
from platypush.message import Message from platypush.message import Message
from platypush.message.response import Response from platypush.message.response import Response
from platypush.utils import get_hash, get_module_and_method_from_action, get_redis_queue_name_by_message, \ from platypush.utils import (
is_functional_procedure get_hash,
get_module_and_method_from_action,
get_redis_queue_name_by_message,
is_functional_procedure,
)
logger = logging.getLogger('platypush') logger = logging.getLogger('platypush')
@ -21,8 +25,17 @@ logger = logging.getLogger('platypush')
class Request(Message): class Request(Message):
"""Request message class""" """Request message class"""
def __init__(self, target, action, origin=None, id=None, backend=None, def __init__(
args=None, token=None, timestamp=None): self,
target,
action,
origin=None,
id=None,
backend=None,
args=None,
token=None,
timestamp=None,
):
""" """
Params: Params:
target -- Target node [Str] target -- Target node [Str]
@ -48,9 +61,13 @@ class Request(Message):
@classmethod @classmethod
def build(cls, msg): def build(cls, msg):
msg = super().parse(msg) msg = super().parse(msg)
args = {'target': msg.get('target', Config.get('device_id')), 'action': msg['action'], args = {
'args': msg.get('args', {}), 'id': msg['id'] if 'id' in msg else cls._generate_id(), 'target': msg.get('target', Config.get('device_id')),
'timestamp': msg['_timestamp'] if '_timestamp' in msg else time.time()} 'action': msg['action'],
'args': msg.get('args', {}),
'id': msg['id'] if 'id' in msg else cls._generate_id(),
'timestamp': msg['_timestamp'] if '_timestamp' in msg else time.time(),
}
if 'origin' in msg: if 'origin' in msg:
args['origin'] = msg['origin'] args['origin'] = msg['origin']
@ -61,7 +78,7 @@ class Request(Message):
@staticmethod @staticmethod
def _generate_id(): def _generate_id():
_id = '' _id = ''
for i in range(0, 16): for _ in range(0, 16):
_id += '%.2x' % random.randint(0, 255) _id += '%.2x' % random.randint(0, 255)
return _id return _id
@ -84,9 +101,14 @@ class Request(Message):
return proc_config(*args, **kwargs) return proc_config(*args, **kwargs)
proc = Procedure.build(name=proc_name, requests=proc_config['actions'], proc = Procedure.build(
_async=proc_config['_async'], args=self.args, name=proc_name,
backend=self.backend, id=self.id) requests=proc_config['actions'],
_async=proc_config['_async'],
args=self.args,
backend=self.backend,
id=self.id,
)
return proc.execute(*args, **kwargs) return proc.execute(*args, **kwargs)
@ -112,7 +134,7 @@ class Request(Message):
if isinstance(value, str): if isinstance(value, str):
value = self.expand_value_from_context(value, **context) value = self.expand_value_from_context(value, **context)
elif isinstance(value, dict) or isinstance(value, list): elif isinstance(value, (dict, list)):
self._expand_context(event_args=value, **context) self._expand_context(event_args=value, **context)
event_args[key] = value event_args[key] = value
@ -132,7 +154,11 @@ class Request(Message):
try: try:
exec('{}="{}"'.format(k, re.sub(r'(^|[^\\])"', '\1\\"', v))) exec('{}="{}"'.format(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(
'Could not set context variable {}={}: {}'.format(
k, v, str(e)
)
)
logger.debug('Context: {}'.format(context)) logger.debug('Context: {}'.format(context))
parsed_value = '' parsed_value = ''
@ -152,7 +178,7 @@ class Request(Message):
if callable(context_value): if callable(context_value):
context_value = context_value() context_value = context_value()
if isinstance(context_value, range) or isinstance(context_value, tuple): if isinstance(context_value, (range, tuple)):
context_value = [*context_value] context_value = [*context_value]
if isinstance(context_value, datetime.date): if isinstance(context_value, datetime.date):
context_value = context_value.isoformat() context_value = context_value.isoformat()
@ -162,7 +188,7 @@ class Request(Message):
parsed_value += prefix + ( parsed_value += prefix + (
json.dumps(context_value) json.dumps(context_value)
if isinstance(context_value, list) or isinstance(context_value, dict) if isinstance(context_value, (list, dict))
else str(context_value) else str(context_value)
) )
else: else:
@ -205,6 +231,9 @@ class Request(Message):
""" """
def _thread_func(_n_tries, errors=None): def _thread_func(_n_tries, errors=None):
from platypush.context import get_bus
from platypush.plugins import RunnablePlugin
response = None response = None
try: try:
@ -221,11 +250,15 @@ class Request(Message):
return response return response
else: else:
action = self.expand_value_from_context(self.action, **context) action = self.expand_value_from_context(self.action, **context)
(module_name, method_name) = get_module_and_method_from_action(action) (module_name, method_name) = get_module_and_method_from_action(
action
)
plugin = get_plugin(module_name) plugin = get_plugin(module_name)
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
msg = 'Uncaught pre-processing exception from action [{}]: {}'.format(self.action, str(e)) msg = 'Uncaught pre-processing exception from action [{}]: {}'.format(
self.action, str(e)
)
logger.warning(msg) logger.warning(msg)
response = Response(output=None, errors=[msg]) response = Response(output=None, errors=[msg])
self._send_response(response) self._send_response(response)
@ -243,24 +276,37 @@ class Request(Message):
response = plugin.run(method_name, args) response = plugin.run(method_name, args)
if not response: if not response:
logger.warning('Received null response from action {}'.format(action)) logger.warning(
'Received null response from action {}'.format(action)
)
else: else:
if response.is_error(): if response.is_error():
logger.warning(('Response processed with errors from ' + logger.warning(
'action {}: {}').format( (
action, str(response))) 'Response processed with errors from ' + 'action {}: {}'
).format(action, str(response))
)
elif not response.disable_logging: elif not response.disable_logging:
logger.info('Processed response from action {}: {}'. logger.info(
format(action, str(response))) 'Processed response from action {}: {}'.format(
action, str(response)
)
)
except (AssertionError, TimeoutError) as e: except (AssertionError, TimeoutError) as e:
plugin.logger.exception(e) plugin.logger.exception(e)
logger.warning('{} from action [{}]: {}'.format(type(e), action, str(e))) logger.warning(
'{} from action [{}]: {}'.format(type(e), action, str(e))
)
response = Response(output=None, errors=[str(e)]) response = Response(output=None, errors=[str(e)])
except Exception as e: except Exception as e:
# Retry mechanism # Retry mechanism
plugin.logger.exception(e) plugin.logger.exception(e)
logger.warning(('Uncaught exception while processing response ' + logger.warning(
'from action [{}]: {}').format(action, str(e))) (
'Uncaught exception while processing response '
+ 'from action [{}]: {}'
).format(action, str(e))
)
errors = errors or [] errors = errors or []
if str(e) not in errors: if str(e) not in errors:
@ -269,16 +315,20 @@ class Request(Message):
response = Response(output=None, errors=errors) response = Response(output=None, errors=errors)
if _n_tries - 1 > 0: if _n_tries - 1 > 0:
logger.info('Reloading plugin {} and retrying'.format(module_name)) logger.info('Reloading plugin {} and retrying'.format(module_name))
get_plugin(module_name, reload=True) plugin = get_plugin(module_name, reload=True)
if isinstance(plugin, RunnablePlugin):
plugin.bus = get_bus()
plugin.start()
response = _thread_func(_n_tries=_n_tries - 1, errors=errors) response = _thread_func(_n_tries=_n_tries - 1, errors=errors)
finally: finally:
self._send_response(response) self._send_response(response)
return response return response
token_hash = Config.get('token_hash') stored_token_hash = Config.get('token_hash')
token = getattr(self, 'token', '')
if token_hash: if stored_token_hash and get_hash(token) != stored_token_hash:
if self.token is None or get_hash(self.token) != token_hash:
raise PermissionError() raise PermissionError()
if _async: if _async:
@ -292,7 +342,8 @@ class Request(Message):
the message into a UTF-8 JSON string the message into a UTF-8 JSON string
""" """
return json.dumps({ return json.dumps(
{
'type': 'request', 'type': 'request',
'target': self.target, 'target': self.target,
'action': self.action, 'action': self.action,
@ -301,6 +352,8 @@ class Request(Message):
'id': self.id if hasattr(self, 'id') else None, 'id': self.id if hasattr(self, 'id') else None,
'token': self.token if hasattr(self, 'token') else None, 'token': self.token if hasattr(self, 'token') else None,
'_timestamp': self.timestamp, '_timestamp': self.timestamp,
}) }
)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: