forked from platypush/platypush
LINT fixes for the utils
module + additional documentation
This commit is contained in:
parent
b8fca97891
commit
4849e14414
1 changed files with 109 additions and 33 deletions
|
@ -8,7 +8,6 @@ import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import rsa
|
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
|
@ -17,14 +16,16 @@ from typing import Optional, Tuple, Union
|
||||||
|
|
||||||
from dateutil import parser, tz
|
from dateutil import parser, tz
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
from rsa.key import PublicKey, PrivateKey
|
from rsa.key import PublicKey, PrivateKey, newkeys
|
||||||
|
|
||||||
logger = logging.getLogger('utils')
|
logger = logging.getLogger('utils')
|
||||||
|
|
||||||
|
|
||||||
def get_module_and_method_from_action(action):
|
def get_module_and_method_from_action(action):
|
||||||
"""Input : action=music.mpd.play
|
"""
|
||||||
Output : ('music.mpd', 'play')"""
|
Input: action=music.mpd.play
|
||||||
|
Output: ('music.mpd', 'play')
|
||||||
|
"""
|
||||||
|
|
||||||
tokens = action.split('.')
|
tokens = action.split('.')
|
||||||
module_name = str.join('.', tokens[:-1])
|
module_name = str.join('.', tokens[:-1])
|
||||||
|
@ -38,22 +39,21 @@ def get_message_class_by_type(msgtype):
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module('platypush.message.' + msgtype)
|
module = importlib.import_module('platypush.message.' + msgtype)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning('Unsupported message type {}'.format(msgtype))
|
logger.warning('Unsupported message type %s', msgtype)
|
||||||
raise RuntimeError(e)
|
raise RuntimeError(e) from e
|
||||||
|
|
||||||
cls_name = msgtype[0].upper() + msgtype[1:]
|
cls_name = msgtype[0].upper() + msgtype[1:]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msgclass = getattr(module, cls_name)
|
msgclass = getattr(module, cls_name)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.warning('No such class in {}: {}'.format(module.__name__, cls_name))
|
logger.warning('No such class in %s: %s', module.__name__, cls_name)
|
||||||
raise RuntimeError(e)
|
raise RuntimeError(e) from e
|
||||||
|
|
||||||
return msgclass
|
return msgclass
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
def get_event_class_by_type(type): # pylint: disable=redefined-builtin
|
||||||
def get_event_class_by_type(type):
|
|
||||||
"""Gets an event class by type name"""
|
"""Gets an event class by type name"""
|
||||||
event_module = importlib.import_module('.'.join(type.split('.')[:-1]))
|
event_module = importlib.import_module('.'.join(type.split('.')[:-1]))
|
||||||
return getattr(event_module, type.split('.')[-1])
|
return getattr(event_module, type.split('.')[-1])
|
||||||
|
@ -66,7 +66,7 @@ def get_plugin_module_by_name(plugin_name):
|
||||||
try:
|
try:
|
||||||
return importlib.import_module('platypush.plugins.' + plugin_name)
|
return importlib.import_module('platypush.plugins.' + plugin_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.error('Cannot import {}: {}'.format(module_name, str(e)))
|
logger.error('Cannot import %s: %s', module_name, e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ def get_plugin_class_by_name(plugin_name):
|
||||||
module, ''.join([_.capitalize() for _ in plugin_name.split('.')]) + 'Plugin'
|
module, ''.join([_.capitalize() for _ in plugin_name.split('.')]) + 'Plugin'
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Cannot import class {}: {}'.format(class_name, str(e)))
|
logger.error('Cannot import class %s: %s', class_name, e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,13 +191,20 @@ def get_decorators(cls, climb_class_hierarchy=False):
|
||||||
return decorators
|
return decorators
|
||||||
|
|
||||||
|
|
||||||
def get_redis_queue_name_by_message(msg):
|
def get_redis_queue_name_by_message(msg) -> Optional[str]:
|
||||||
from platypush.message import Message
|
"""
|
||||||
|
Get the Redis queue name for the response(s) associated to a request
|
||||||
|
message.
|
||||||
|
|
||||||
if not isinstance(msg, Message):
|
:param msg: Input message, as a :class:`platypush.message.request.Request`
|
||||||
logger.warning('Not a valid message (type: {}): {}'.format(type(msg), msg))
|
object.
|
||||||
|
"""
|
||||||
|
from platypush.message.request import Request
|
||||||
|
|
||||||
return 'platypush/responses/{}'.format(msg.id) if msg.id else None
|
if not isinstance(msg, Request):
|
||||||
|
logger.warning('Not a valid request (type: %s): %s', type(msg), msg)
|
||||||
|
return None
|
||||||
|
return f'platypush/responses/{msg.id}' if msg.id else None
|
||||||
|
|
||||||
|
|
||||||
def _get_ssl_context(
|
def _get_ssl_context(
|
||||||
|
@ -220,6 +227,9 @@ def _get_ssl_context(
|
||||||
|
|
||||||
|
|
||||||
def get_ssl_context(ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None):
|
def get_ssl_context(ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None):
|
||||||
|
"""
|
||||||
|
Generic builder for SSL context.
|
||||||
|
"""
|
||||||
return _get_ssl_context(
|
return _get_ssl_context(
|
||||||
context_type=None,
|
context_type=None,
|
||||||
ssl_cert=ssl_cert,
|
ssl_cert=ssl_cert,
|
||||||
|
@ -232,6 +242,9 @@ def get_ssl_context(ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=Non
|
||||||
def get_ssl_server_context(
|
def get_ssl_server_context(
|
||||||
ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None
|
ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Builder for a server-side SSL context.
|
||||||
|
"""
|
||||||
return _get_ssl_context(
|
return _get_ssl_context(
|
||||||
context_type=ssl.PROTOCOL_TLS_SERVER,
|
context_type=ssl.PROTOCOL_TLS_SERVER,
|
||||||
ssl_cert=ssl_cert,
|
ssl_cert=ssl_cert,
|
||||||
|
@ -244,6 +257,9 @@ def get_ssl_server_context(
|
||||||
def get_ssl_client_context(
|
def get_ssl_client_context(
|
||||||
ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None
|
ssl_cert=None, ssl_key=None, ssl_cafile=None, ssl_capath=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Builder for a client-side SSL context.
|
||||||
|
"""
|
||||||
return _get_ssl_context(
|
return _get_ssl_context(
|
||||||
context_type=ssl.PROTOCOL_TLS_CLIENT,
|
context_type=ssl.PROTOCOL_TLS_CLIENT,
|
||||||
ssl_cert=ssl_cert,
|
ssl_cert=ssl_cert,
|
||||||
|
@ -253,19 +269,22 @@ def get_ssl_client_context(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_thread_name(name):
|
def set_thread_name(name: str):
|
||||||
global logger
|
"""
|
||||||
|
Set the name of the current thread.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
import prctl
|
import prctl
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
prctl.set_name(name) # pylint: disable=no-member
|
||||||
prctl.set_name(name)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.debug('Unable to set thread name: prctl module is missing')
|
logger.debug('Unable to set thread name: prctl module is missing')
|
||||||
|
|
||||||
|
|
||||||
def find_bins_in_path(bin_name):
|
def find_bins_in_path(bin_name):
|
||||||
|
"""
|
||||||
|
Search for a binary in the PATH variable.
|
||||||
|
"""
|
||||||
return [
|
return [
|
||||||
os.path.join(p, bin_name)
|
os.path.join(p, bin_name)
|
||||||
for p in os.environ.get('PATH', '').split(':')
|
for p in os.environ.get('PATH', '').split(':')
|
||||||
|
@ -276,14 +295,14 @@ def find_bins_in_path(bin_name):
|
||||||
|
|
||||||
def find_files_by_ext(directory, *exts):
|
def find_files_by_ext(directory, *exts):
|
||||||
"""
|
"""
|
||||||
Finds all the files in the given directory with the provided extensions
|
Finds all the files in the given directory with the provided extensions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not exts:
|
if not exts:
|
||||||
raise AttributeError('No extensions provided')
|
raise AttributeError('No extensions provided')
|
||||||
|
|
||||||
if not os.path.isdir(directory):
|
if not os.path.isdir(directory):
|
||||||
raise AttributeError('{} is not a valid directory'.format(directory))
|
raise AttributeError(f'{directory} is not a valid directory')
|
||||||
|
|
||||||
min_len = len(min(exts, key=len))
|
min_len = len(min(exts, key=len))
|
||||||
max_len = len(max(exts, key=len))
|
max_len = len(max(exts, key=len))
|
||||||
|
@ -296,7 +315,11 @@ def find_files_by_ext(directory, *exts):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_process_alive(pid):
|
def is_process_alive(pid: int) -> bool:
|
||||||
|
"""
|
||||||
|
:param pid: Process ID.
|
||||||
|
:return: True if the process with the given PID is alive.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
os.kill(pid, 0)
|
os.kill(pid, 0)
|
||||||
return True
|
return True
|
||||||
|
@ -304,7 +327,10 @@ def is_process_alive(pid):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_ip_or_hostname():
|
def get_ip_or_hostname() -> str:
|
||||||
|
"""
|
||||||
|
Get the the default IP address or hostname of the machine.
|
||||||
|
"""
|
||||||
ip = socket.gethostbyname(socket.gethostname())
|
ip = socket.gethostbyname(socket.gethostname())
|
||||||
if ip.startswith('127.') or ip.startswith('::1'):
|
if ip.startswith('127.') or ip.startswith('::1'):
|
||||||
try:
|
try:
|
||||||
|
@ -318,11 +344,18 @@ def get_ip_or_hostname():
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def get_mime_type(resource):
|
def get_mime_type(resource: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the MIME type of the given resource.
|
||||||
|
|
||||||
|
:param resource: The resource to get the MIME type for - it can be a file
|
||||||
|
path or a URL.
|
||||||
|
"""
|
||||||
import magic
|
import magic
|
||||||
|
|
||||||
if resource.startswith('file://'):
|
if resource.startswith('file://'):
|
||||||
resource = resource[len('file://') :]
|
offset = len('file://')
|
||||||
|
resource = resource[offset:]
|
||||||
|
|
||||||
# noinspection HttpUrlsUsage
|
# noinspection HttpUrlsUsage
|
||||||
if resource.startswith('http://') or resource.startswith('https://'):
|
if resource.startswith('http://') or resource.startswith('https://'):
|
||||||
|
@ -341,8 +374,13 @@ def get_mime_type(resource):
|
||||||
if mime:
|
if mime:
|
||||||
return mime.mime_type if hasattr(mime, 'mime_type') else mime
|
return mime.mime_type if hasattr(mime, 'mime_type') else mime
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def camel_case_to_snake_case(string):
|
def camel_case_to_snake_case(string):
|
||||||
|
"""
|
||||||
|
Utility function to convert CamelCase to snake_case.
|
||||||
|
"""
|
||||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', string)
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', string)
|
||||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||||
|
|
||||||
|
@ -364,18 +402,34 @@ def grouper(n, iterable, fillvalue=None):
|
||||||
|
|
||||||
|
|
||||||
def is_functional_procedure(obj) -> bool:
|
def is_functional_procedure(obj) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the given object is a functional procedure.
|
||||||
|
"""
|
||||||
return callable(obj) and hasattr(obj, 'procedure')
|
return callable(obj) and hasattr(obj, 'procedure')
|
||||||
|
|
||||||
|
|
||||||
def is_functional_hook(obj) -> bool:
|
def is_functional_hook(obj) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the given object is a functional hook.
|
||||||
|
"""
|
||||||
return callable(obj) and hasattr(obj, 'hook')
|
return callable(obj) and hasattr(obj, 'hook')
|
||||||
|
|
||||||
|
|
||||||
def is_functional_cron(obj) -> bool:
|
def is_functional_cron(obj) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the given object is a functional cron.
|
||||||
|
"""
|
||||||
return callable(obj) and hasattr(obj, 'cron') and hasattr(obj, 'cron_expression')
|
return callable(obj) and hasattr(obj, 'cron') and hasattr(obj, 'cron_expression')
|
||||||
|
|
||||||
|
|
||||||
def run(action, *args, **kwargs):
|
def run(action, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run the given action with the given arguments. Example:
|
||||||
|
|
||||||
|
>>> from platypush.utils import run
|
||||||
|
>>> run('music.mpd.play', resource='file:///home/user/music.mp3')
|
||||||
|
|
||||||
|
"""
|
||||||
from platypush.context import get_plugin
|
from platypush.context import get_plugin
|
||||||
|
|
||||||
(module_name, method_name) = get_module_and_method_from_action(action)
|
(module_name, method_name) = get_module_and_method_from_action(action)
|
||||||
|
@ -410,13 +464,13 @@ def generate_rsa_key_pair(
|
||||||
:return: A tuple with the generated ``(priv_key_str, pub_key_str)``.
|
:return: A tuple with the generated ``(priv_key_str, pub_key_str)``.
|
||||||
"""
|
"""
|
||||||
logger.info('Generating RSA keypair')
|
logger.info('Generating RSA keypair')
|
||||||
pub_key, priv_key = rsa.newkeys(size)
|
pub_key, priv_key = newkeys(size)
|
||||||
logger.info('Generated RSA keypair')
|
logger.info('Generated RSA keypair')
|
||||||
public_key_str = pub_key.save_pkcs1('PEM').decode()
|
public_key_str = pub_key.save_pkcs1('PEM').decode()
|
||||||
private_key_str = priv_key.save_pkcs1('PEM').decode()
|
private_key_str = priv_key.save_pkcs1('PEM').decode()
|
||||||
|
|
||||||
if key_file:
|
if key_file:
|
||||||
logger.info('Saving private key to {}'.format(key_file))
|
logger.info('Saving private key to %s', key_file)
|
||||||
with open(os.path.expanduser(key_file), 'w') as f1, open(
|
with open(os.path.expanduser(key_file), 'w') as f1, open(
|
||||||
os.path.expanduser(key_file) + '.pub', 'w'
|
os.path.expanduser(key_file) + '.pub', 'w'
|
||||||
) as f2:
|
) as f2:
|
||||||
|
@ -428,6 +482,9 @@ def generate_rsa_key_pair(
|
||||||
|
|
||||||
|
|
||||||
def get_or_generate_jwt_rsa_key_pair():
|
def get_or_generate_jwt_rsa_key_pair():
|
||||||
|
"""
|
||||||
|
Get or generate a JWT RSA key pair.
|
||||||
|
"""
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
|
|
||||||
key_dir = os.path.join(Config.get('workdir'), 'jwt')
|
key_dir = os.path.join(Config.get('workdir'), 'jwt')
|
||||||
|
@ -437,8 +494,8 @@ def get_or_generate_jwt_rsa_key_pair():
|
||||||
if os.path.isfile(priv_key_file) and os.path.isfile(pub_key_file):
|
if os.path.isfile(priv_key_file) and os.path.isfile(pub_key_file):
|
||||||
with open(pub_key_file, 'r') as f1, open(priv_key_file, 'r') as f2:
|
with open(pub_key_file, 'r') as f1, open(priv_key_file, 'r') as f2:
|
||||||
return (
|
return (
|
||||||
rsa.PublicKey.load_pkcs1(f1.read().encode()),
|
PublicKey.load_pkcs1(f1.read().encode()),
|
||||||
rsa.PrivateKey.load_pkcs1(f2.read().encode()),
|
PrivateKey.load_pkcs1(f2.read().encode()),
|
||||||
)
|
)
|
||||||
|
|
||||||
pathlib.Path(key_dir).mkdir(parents=True, exist_ok=True, mode=0o755)
|
pathlib.Path(key_dir).mkdir(parents=True, exist_ok=True, mode=0o755)
|
||||||
|
@ -446,6 +503,12 @@ def get_or_generate_jwt_rsa_key_pair():
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_plugins() -> dict:
|
def get_enabled_plugins() -> dict:
|
||||||
|
"""
|
||||||
|
Get the enabled plugins.
|
||||||
|
|
||||||
|
:return: A dictionary with the enabled plugins, in the format ``name`` ->
|
||||||
|
:class:`platypush.plugins.Plugin` instance.
|
||||||
|
"""
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
from platypush.context import get_plugin
|
from platypush.context import get_plugin
|
||||||
|
|
||||||
|
@ -456,13 +519,22 @@ def get_enabled_plugins() -> dict:
|
||||||
if plugin:
|
if plugin:
|
||||||
plugins[name] = plugin
|
plugins[name] = plugin
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f'Could not initialize plugin {name}')
|
logger.warning('Could not initialize plugin %s', name)
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
def get_redis() -> Redis:
|
def get_redis() -> Redis:
|
||||||
|
"""
|
||||||
|
Get a Redis client on the basis of the Redis configuration.
|
||||||
|
|
||||||
|
The Redis configuration can be loaded from:
|
||||||
|
|
||||||
|
1. The ``backend.redis`` configuration (``redis_args`` attribute)
|
||||||
|
2. The ``redis`` plugin.
|
||||||
|
|
||||||
|
"""
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
|
|
||||||
return Redis(
|
return Redis(
|
||||||
|
@ -475,6 +547,10 @@ def get_redis() -> Redis:
|
||||||
|
|
||||||
|
|
||||||
def to_datetime(t: Union[str, int, float, datetime.datetime]) -> datetime.datetime:
|
def to_datetime(t: Union[str, int, float, datetime.datetime]) -> datetime.datetime:
|
||||||
|
"""
|
||||||
|
Utility function to convert a datetime/timestamp provided as a
|
||||||
|
string/integer/float/datetime to a ``datetime.datetime`` instance.
|
||||||
|
"""
|
||||||
if isinstance(t, (int, float)):
|
if isinstance(t, (int, float)):
|
||||||
return datetime.datetime.fromtimestamp(t, tz=tz.tzutc())
|
return datetime.datetime.fromtimestamp(t, tz=tz.tzutc())
|
||||||
if isinstance(t, str):
|
if isinstance(t, str):
|
||||||
|
|
Loading…
Reference in a new issue